├── .github └── workflows │ ├── build.yml │ └── doc.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── core ├── build.gradle.kts └── src │ └── main │ ├── java │ └── top │ │ └── iseason │ │ └── bukkittemplate │ │ ├── BukkitPlugin.java │ │ ├── BukkitTemplate.java │ │ ├── DisableHook.java │ │ ├── PluginYmlRuntime.java │ │ ├── runtime │ │ ├── ClassAppender.java │ │ ├── RuntimeBuilder.java │ │ ├── RuntimeManager.java │ │ ├── XmlParser.java │ │ └── loader │ │ │ ├── AppendableClassLoader.java │ │ │ └── IsolatedClassLoader.java │ │ └── utils │ │ ├── JavaVersion.java │ │ └── ReflectionUtil.java │ └── kotlin │ └── top │ └── iseason │ └── bukkittemplate │ ├── command │ ├── CommandHandler.kt │ ├── CommandNode.kt │ ├── CommandNodeExecutor.kt │ ├── ParamAdopter.kt │ ├── ParmaException.kt │ └── Parmas.kt │ ├── config │ ├── ConfigKey.kt │ ├── ConfigWatcher.kt │ ├── DatabaseConfig.kt │ ├── Lang.kt │ ├── SimpleYAMLConfig.kt │ ├── annotations │ │ ├── Comment.kt │ │ ├── FilePath.kt │ │ └── Key.kt │ └── type │ │ ├── ConfigType.kt │ │ ├── DefaultConfigType.kt │ │ ├── EnumSetType.kt │ │ ├── EnumType.kt │ │ ├── ListType.kt │ │ ├── MapType.kt │ │ └── SetType.kt │ ├── debug │ └── SimpleLogger.kt │ ├── hook │ ├── BaseHook.kt │ ├── BungeeCordHook.kt │ └── PlaceHolderHook.kt │ ├── ui │ ├── UIBuilder.kt │ ├── UIListener.kt │ ├── container │ │ ├── BaseUI.kt │ │ ├── ChestUI.kt │ │ ├── LazyUIContainer.kt │ │ ├── Pageable.kt │ │ └── UIContainer.kt │ └── slot │ │ ├── BaseSlot.kt │ │ ├── Button.kt │ │ ├── ClickSlot.kt │ │ ├── IOSlot.kt │ │ └── Icon.kt │ └── utils │ ├── bukkit │ ├── EntityUtils.kt │ ├── EventUtils.kt │ ├── IOUtils.kt │ ├── ItemUtils.kt │ ├── LocationUtils.kt │ └── MessageUtils.kt │ └── other │ ├── CoolDown.kt │ ├── LagCatcher.kt │ ├── NumberUtils.kt │ ├── RandomUtils.kt │ └── Submit.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── build.gradle.kts ├── libs │ ├── BanItem_with_NBT_v3.2.37.jar │ ├── GermPlugin-API.jar │ ├── GlobalMarketPlus-API.jar │ ├── InvSync-2.3.42-API.jar │ └── PlayerDataSQL_v1.2.22.jar └── src │ └── main │ ├── java │ ├── com │ │ └── github │ │ │ └── mgunlogson │ │ │ └── cuckoofilter4j │ │ │ ├── ArrayUtil.java │ │ │ ├── Constants.java │ │ │ ├── CuckooFilter.java │ │ │ ├── FilterTable.java │ │ │ ├── LongBitSet.java │ │ │ ├── RamUsageEstimator.java │ │ │ ├── SegmentedBucketLocker.java │ │ │ └── Utils.java │ └── top │ │ └── iseason │ │ └── bukkit │ │ └── sakurabind │ │ ├── cuckoofilter │ │ ├── AbstractCuckooStrategy.java │ │ ├── BloomFilter.java │ │ ├── CuckooFilter.java │ │ ├── CuckooStrategies.java │ │ ├── CuckooStrategy.java │ │ ├── CuckooStrategyMurmurBealDupras32.java │ │ ├── CuckooTable.java │ │ └── ProbabilisticFilter.java │ │ └── event │ │ ├── AutoBindMessageEvent.java │ │ ├── BindEvent.java │ │ ├── BlockBindEvent.java │ │ ├── BlockBindFromItemEvent.java │ │ ├── BlockUnBindEvent.java │ │ ├── EntityBindEvent.java │ │ ├── EntityUnBindEvent.java │ │ ├── ItemBindEvent.java │ │ ├── ItemBindFromBlockEvent.java │ │ ├── ItemMatchedEvent.java │ │ ├── ItemSendBackEvent.java │ │ ├── ItemUnBIndEvent.java │ │ ├── PlayerDenyMessageEvent.java │ │ └── UnBindEvent.java │ ├── kotlin │ └── top │ │ └── iseason │ │ └── bukkit │ │ └── sakurabind │ │ ├── SakuraBind.kt │ │ ├── SakuraBindAPI.kt │ │ ├── cache │ │ ├── BaseCache.kt │ │ ├── BlockCache.kt │ │ ├── BlockInfo.kt │ │ ├── CacheManager.kt │ │ ├── EntityCache.kt │ │ └── FallingBlockCache.kt │ │ ├── command │ │ ├── AutoBindCommand.kt │ │ ├── BindAllCommand.kt │ │ ├── BindCommand.kt │ │ ├── BindToCommand.kt │ │ ├── CallbackCommand.kt │ │ ├── DebugCommand.kt │ │ ├── GetLostCommand.kt │ │ ├── NBTCommand.kt │ │ ├── OpenLostCommand.kt │ │ ├── ReloadCommand.kt │ │ ├── RootCommand.kt │ │ ├── SelectCommand.kt │ │ ├── SuperCallbackCommand.kt │ │ ├── TestCacheCommand.kt │ │ ├── TestCommand.kt │ │ ├── TestMatchCommand.kt │ │ ├── TestTryMatchCommand.kt │ │ ├── UnBindAllCommand.kt │ │ └── UnBindCommand.kt │ │ ├── config │ │ ├── BaseSetting.kt │ │ ├── BindItemConfig.kt │ │ ├── BindLogger.kt │ │ ├── Config.kt │ │ ├── DefaultItemSetting.kt │ │ ├── GlobalSettings.kt │ │ ├── ItemSetting.kt │ │ ├── ItemSettings.kt │ │ ├── Lang.kt │ │ ├── SendBackLogger.kt │ │ ├── UniqueItemConfig.kt │ │ └── matcher │ │ │ ├── BaseMatcher.kt │ │ │ ├── LoreMatcher.kt │ │ │ ├── MatcherManager.kt │ │ │ ├── NBTMatcher.kt │ │ │ ├── NameMatcher.kt │ │ │ └── TypeMatcher.kt │ │ ├── dto │ │ ├── BindLogs.kt │ │ ├── PlayerItems.kt │ │ ├── SendBackLogs.kt │ │ └── UniqueLogs.kt │ │ ├── hook │ │ ├── AuthMeHook.kt │ │ ├── BanItemHook.kt │ │ ├── GermHook.kt │ │ ├── GlobalMarketPlusHook.kt │ │ ├── HuskSyncHook.kt │ │ ├── InvSyncHook.kt │ │ ├── ItemsAdderHook.kt │ │ ├── MMOItemsHook.kt │ │ ├── McMMoHook.kt │ │ ├── OraxenHook.kt │ │ ├── PlaceHolderExpansion.kt │ │ ├── PlayerDataSQLHook.kt │ │ └── SweetMailHook.kt │ │ ├── listener │ │ ├── BindActionListener.kt │ │ ├── BlockListener.kt │ │ ├── BlockListener1132.kt │ │ ├── EntityListener.kt │ │ ├── ItemListener.kt │ │ ├── ItemListener16.kt │ │ ├── ItemListener194.kt │ │ ├── LegacyPickupItemListener.kt │ │ ├── LoginAuthMeListener.kt │ │ ├── LoginListener.kt │ │ ├── PaperListener.kt │ │ ├── PickupItemListener.kt │ │ └── SelectListener.kt │ │ ├── module │ │ ├── BindItem.kt │ │ └── UniqueItem.kt │ │ ├── pickers │ │ ├── BasePicker.kt │ │ ├── DataBasePicker.kt │ │ ├── EnderChestPicker.kt │ │ ├── GlobalMarketPlusPicker.kt │ │ ├── PlayerInvPicker.kt │ │ └── SweetMailPicker.kt │ │ ├── task │ │ ├── DelaySender.kt │ │ ├── DropItemList.kt │ │ ├── EntityRemoveQueue.kt │ │ ├── FallingList.kt │ │ ├── MigrationScanner.kt │ │ └── Scanner.kt │ │ └── utils │ │ ├── BindType.kt │ │ ├── Defenders.kt │ │ ├── ListUtil.kt │ │ ├── MessageTool.kt │ │ ├── PlayerTool.kt │ │ └── SendBackType.kt │ └── resources │ ├── placeholders.txt │ └── plugin.yml └── settings.gradle.kts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: AutoReleaseJar 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: 17 21 | distribution: temurin 22 | - name: Cache .gradle/wrapper 23 | uses: actions/cache@v3 24 | with: 25 | path: ~/.gradle/wrapper 26 | key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/*.gradle') }} 27 | restore-keys: ${{ runner.os }}-gradle-wrapper- 28 | 29 | - name: Grant execute permission for gradlew 30 | run: chmod +x gradlew 31 | 32 | - name: Build with Gradle 33 | uses: gradle/gradle-build-action@v2 34 | with: 35 | arguments: clean build 36 | - name: Upload Artifacts 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: SakuraBind Artifact 40 | path: build/*.jar 41 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | persist-credentials: false 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: 21 19 | distribution: 'adopt' 20 | - name: Permission 21 | run: chmod +rx ./gradlew 22 | 23 | - name: Build documentation 24 | run: ./gradlew dokkaHtml 25 | 26 | - name: Publish documentation 27 | uses: JamesIves/github-pages-deploy-action@releases/v4 28 | with: 29 | BRANCH: gh-pages 30 | FOLDER: plugin/build/dokka/html -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea 9 | .idea/modules.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SakuraBind 2 | 3 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=Iseason2000_SakuraBind&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=Iseason2000_SakuraBind)[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Iseason2000_SakuraBind&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=Iseason2000_SakuraBind)[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Iseason2000_SakuraBind&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=Iseason2000_SakuraBind) 4 | 5 | ## 插件介绍 6 | 7 | > SakuraBind源自自用的绑定插件,功能有限,经过一系列功能定制之后我决定完善其功能并发布出来。 8 | > 9 | > 本插件会尽量涵盖所有有用的功能,并提供丰富的配置尽可能满足您的所有需求 10 | 11 | ### 特点 12 | 13 | * 支持 Minecraft 1.8+的服务端。包括 spigot、paper、甚至catserver等mod服务器端 14 | * 多级配置。从权限到某类特殊物品的配置到全局统一配置灵活控制绑定物品行为 15 | * 极致性能优化。通过内存TTL、NBT、文件存储三级缓存以减少物品匹配性能损耗,并利用布谷鸟过滤器处理缓存穿透问题 16 | * 方块绑定支持。支持多方快结构、装饰物,无论是流体、爆炸、活塞、丢失支撑方块等造成的方块物品变成掉落物绑定依旧存在 17 | * 丢失物品找回功能。掉落物掉虚空、仙人掌、岩浆、被他人拿走甚至在容器中被破坏,都可以将物品送回玩家背包中,如玩家不在线则存入暂存箱中 18 | * 全面PlaceHolderAPI支持。任何看得见的地方都能使用papi,如消息、lore 19 | * 实体绑定功能, 支持刷怪蛋绑定实体 20 | * 自动绑定功能, 名字、lore、材质、nbt多种方式自由组合识别绑定 21 | * 多数据库支持(暂存箱)。支持 SQLite(本地|默认)、MySQL、MariaDB、Oracle、PostgreSQL、SQLServer 22 | * 插件在设计之初就考虑兼容性,理论上兼容大部分插件,可以提issue兼容 23 | * 监听器开关。可通过关闭不需要的功能减少性能损耗 24 | * 物品 -> 方块 -> 实体 -> 物品 全链路追踪绑定 25 | 26 | 插件文档: [https://iseason2000.github.io/docs/category/sakurabind](https://iseason2000.github.io/docs/category/sakurabind) 27 | 28 | ![](https://bstats.org/signatures/bukkit/SakuraBind.svg) 29 | 30 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | id("com.gradleup.shadow") 4 | } 5 | 6 | subprojects { 7 | group = rootProject.group 8 | version = rootProject.version 9 | apply { 10 | plugin() 11 | plugin() 12 | } 13 | repositories { 14 | maven { 15 | name = "aliyun" 16 | url = uri("https://maven.aliyun.com/repository/public") 17 | } 18 | maven { 19 | name = "aliyun-google" 20 | url = uri("https://maven.aliyun.com/repository/google") 21 | } 22 | mavenCentral() 23 | // google() 24 | maven { 25 | name = "spigot" 26 | url = uri("https://hub.spigotmc.org/nexus/content/repositories/public/") 27 | } 28 | maven { 29 | name = "papermc" 30 | url = uri("https://repo.papermc.io/repository/maven-public/") 31 | } 32 | maven { 33 | name = "jitpack" 34 | url = uri("https://jitpack.io") 35 | } 36 | maven { 37 | name = "CodeMC" 38 | url = uri("https://repo.codemc.org/repository/maven-public") 39 | } 40 | maven { 41 | name = "PlaceholderAPI" 42 | url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") 43 | } 44 | mavenLocal() 45 | } 46 | dependencies { 47 | val kotlinVersion: String by rootProject 48 | val exposedVersion: String by rootProject 49 | compileOnly("de.tr7zw:item-nbt-api-plugin:2.13.2") 50 | compileOnly(platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")) 51 | //基础库 52 | compileOnly(kotlin("stdlib")) 53 | compileOnly("org.spigotmc", "spigot-api", "1.20.3-R0.1-SNAPSHOT") 54 | // compileOnly("org.spigotmc", "spigot-api", "1.16.5-R0.1-SNAPSHOT") 55 | compileOnly( 56 | "com.destroystokyo.paper", "paper-api", "1.16.5-R0.1-SNAPSHOT" 57 | ) { 58 | isTransitive = false 59 | exclude("org.bukkit") 60 | } 61 | compileOnly("me.clip:placeholderapi:2.11.6") 62 | // implementation("io.github.bananapuncher714:nbteditor:7.19.3") 63 | 64 | // 数据库 65 | compileOnly("org.jetbrains.exposed", "exposed-core", exposedVersion) 66 | compileOnly("org.jetbrains.exposed", "exposed-dao", exposedVersion) 67 | compileOnly("org.jetbrains.exposed", "exposed-jdbc", exposedVersion) 68 | compileOnly("org.jetbrains.exposed", "exposed-java-time", exposedVersion) 69 | compileOnly("com.zaxxer:HikariCP:4.0.3") 70 | } 71 | 72 | 73 | } 74 | 75 | repositories { 76 | // 阿里的服务器速度快一点 77 | maven { 78 | name = "aliyun" 79 | url = uri("https://maven.aliyun.com/repository/public/") 80 | } 81 | google() 82 | mavenCentral() 83 | } 84 | dependencies { 85 | //基础库 86 | compileOnly(kotlin("stdlib")) 87 | } 88 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | // id("org.jetbrains.dokka") version "1.9.20" 4 | } 5 | 6 | group = "top.iseason.bukkittemplate" 7 | 8 | val exposedVersion: String by rootProject 9 | repositories { 10 | mavenCentral() 11 | maven { 12 | name = "MMOItems" 13 | url = uri("https://nexus.phoenixdevt.fr/repository/maven-public/") 14 | } 15 | maven { 16 | name = "Oraxen" 17 | url = uri("https://repo.oraxen.com/releases") 18 | } 19 | maven { 20 | name = "PlaceholderAPI" 21 | url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") 22 | } 23 | } 24 | dependencies { 25 | // compileOnly("org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT") 26 | // dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.9.20") 27 | 28 | compileOnly("net.kyori:adventure-text-minimessage:4.18.0") 29 | compileOnly("net.kyori:adventure-platform-bukkit:4.3.4") 30 | implementation("org.bstats:bstats-bukkit:3.0.2") 31 | 32 | compileOnly("net.Indyuce:MMOItems-API:6.9.4-SNAPSHOT") { isTransitive = false } 33 | compileOnly("com.github.LoneDev6:api-itemsadder:3.6.1") { isTransitive = false } 34 | compileOnly("io.th0rgal:oraxen:1.189.0") { isTransitive = false } 35 | } 36 | tasks { 37 | kotlin { 38 | jvmToolchain(8) 39 | } 40 | 41 | compileJava { 42 | options.encoding = "UTF-8" 43 | options.isFailOnError = false 44 | } 45 | // dokkaHtml.configure { 46 | // dokkaSourceSets { 47 | // named("main") { 48 | // moduleName.set("BukkitTemplate") 49 | // } 50 | // } 51 | // } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/BukkitPlugin.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | 5 | /** 6 | * 框架插件入口 7 | */ 8 | public interface BukkitPlugin { 9 | 10 | default JavaPlugin getJavaPlugin() { 11 | return BukkitTemplate.getPlugin(); 12 | } 13 | 14 | /** 15 | * 在其他线程加载,比onEnable先调用,结束了才调用onEnable 16 | */ 17 | default void onLoad() { 18 | } 19 | 20 | /** 21 | * 在插件启用后运行 22 | */ 23 | default void onEnable() { 24 | } 25 | 26 | /** 27 | * 在插件启用后异步运行 28 | */ 29 | default void onAsyncEnable() { 30 | } 31 | 32 | /** 33 | * 在插件停用时运行 34 | */ 35 | default void onDisable() { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/DisableHook.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.ConcurrentLinkedQueue; 5 | 6 | /** 7 | * 在插件 onDisable 时自动调用 onDisable() 方法 8 | */ 9 | public final class DisableHook { 10 | 11 | private static final Queue tasks = new ConcurrentLinkedQueue<>(); 12 | 13 | /** 14 | * 运行所有注册的任务,会在插件 onDisable 时自动调用 15 | */ 16 | public static void disableAll() { 17 | for (DisableTask task : tasks) { 18 | try { 19 | task.onDisable(); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | tasks.clear(); 25 | } 26 | 27 | /** 28 | * 添加一个在插件onDisable 时调用的任务 29 | * 30 | * @param task 任务 31 | */ 32 | public static void addTask(DisableTask task) { 33 | tasks.add(task); 34 | } 35 | 36 | public interface DisableTask { 37 | void onDisable(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/PluginYmlRuntime.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate; 2 | 3 | import org.bukkit.configuration.ConfigurationSection; 4 | import org.bukkit.configuration.file.YamlConfiguration; 5 | import top.iseason.bukkittemplate.runtime.ClassAppender; 6 | import top.iseason.bukkittemplate.runtime.RuntimeManager; 7 | 8 | import java.io.File; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | import java.net.URLDecoder; 12 | import java.util.List; 13 | import java.util.jar.JarEntry; 14 | import java.util.jar.JarFile; 15 | 16 | public class PluginYmlRuntime { 17 | 18 | /** 19 | * 解析下载plugin.yml中的依赖 20 | */ 21 | public static RuntimeManager parsePluginYml() throws RuntimeException { 22 | 23 | YamlConfiguration yml = null; 24 | // 为什么不用 classloader 的 getResource呢,因为某些sb系统或者服务端会乱改 25 | // 导致 getResource 的内容错误, 已测试 Debian + CatServer 26 | String location = PluginYmlRuntime.class.getProtectionDomain().getCodeSource().getLocation().getPath(); 27 | try (JarFile jarFile = new JarFile(URLDecoder.decode(location, "UTF-8"), false)) { 28 | JarEntry entry = jarFile.getJarEntry("plugin.yml"); 29 | InputStream resource = jarFile.getInputStream(entry); 30 | yml = YamlConfiguration.loadConfiguration(new InputStreamReader(resource)); 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | if (yml == null) return null; 35 | ConfigurationSection libConfigs = yml.getConfigurationSection("runtime-libraries"); 36 | if (libConfigs == null) return null; 37 | String folder = libConfigs.getString("libraries-folder"); 38 | File parent = new File("libraries"); 39 | if (folder != null) { 40 | if (folder.toLowerCase().startsWith("@plugin:")) { 41 | parent = new File(BukkitTemplate.getPlugin().getDataFolder(), folder.substring(8)); 42 | } else { 43 | parent = new File(folder); 44 | } 45 | } 46 | List repositories = libConfigs.getStringList("repositories"); 47 | if (repositories.isEmpty()) { 48 | repositories.add("https://repo.maven.apache.org/maven2/"); 49 | } 50 | List libraries = libConfigs.getStringList("libraries"); 51 | List assemblyList = libConfigs.getStringList("assembly"); 52 | List excludes = libConfigs.getStringList("excludes"); 53 | ClassAppender classAppender = new ClassAppender(PluginYmlRuntime.class.getClassLoader().getParent()); 54 | RuntimeManager runtimeManager = new RuntimeManager(parent, classAppender, repositories, libraries, assemblyList, libConfigs.getBoolean("parallel")); 55 | runtimeManager.addExcludes(excludes); 56 | try { 57 | runtimeManager.injectTo(PluginYmlRuntime.class.getClassLoader()); 58 | } catch (NoSuchFieldException | IllegalAccessException e) { 59 | e.printStackTrace(); 60 | throw new RuntimeException("运行环境注入失败!"); 61 | } 62 | RuntimeManager.logger = BukkitTemplate.getPlugin().getLogger(); 63 | runtimeManager.downloadAll(); 64 | return runtimeManager; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/runtime/ClassAppender.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.runtime; 2 | 3 | import top.iseason.bukkittemplate.runtime.loader.AppendableClassLoader; 4 | import top.iseason.bukkittemplate.runtime.loader.IsolatedClassLoader; 5 | import top.iseason.bukkittemplate.utils.ReflectionUtil; 6 | 7 | import java.net.URL; 8 | 9 | /** 10 | * 给 ClassLoader 动态添加Url 11 | */ 12 | public class ClassAppender { 13 | 14 | private final AppendableClassLoader assemblyClassLoader; 15 | private final IsolatedClassLoader isolatedClassLoader; 16 | 17 | /** 18 | * @param parent 注入依赖的父类, 如果你想给 A 添加运行环境,那么应该传入 A.getParent() 19 | */ 20 | public ClassAppender(ClassLoader parent) { 21 | AppendableClassLoader appendableClassLoader = new AppendableClassLoader(parent); 22 | IsolatedClassLoader isolatedClassLoader = new IsolatedClassLoader(appendableClassLoader); 23 | this.assemblyClassLoader = appendableClassLoader; 24 | this.isolatedClassLoader = isolatedClassLoader; 25 | } 26 | 27 | /** 28 | * @param assemblyClassLoader 必须是 URLClassLoader 的子类 29 | * @param isolatedClassLoader 30 | */ 31 | public ClassAppender(AppendableClassLoader assemblyClassLoader, IsolatedClassLoader isolatedClassLoader) { 32 | this.assemblyClassLoader = assemblyClassLoader; 33 | this.isolatedClassLoader = isolatedClassLoader; 34 | } 35 | 36 | /** 37 | * 将URl添加进插件的ClassLoader 38 | */ 39 | public synchronized void addIsolatedURL(URL url) { 40 | if (url == null) return; 41 | isolatedClassLoader.addURL(url); 42 | } 43 | 44 | /** 45 | * 将URl添加进插件的ClassLoader 46 | */ 47 | public synchronized void addAssemblyURL(URL url) { 48 | if (url == null) return; 49 | assemblyClassLoader.addURL(url); 50 | } 51 | 52 | /** 53 | * 将运行环境注入至某个ClassLoader下 54 | * 55 | * @param classLoader 目标ClassLoader 56 | */ 57 | public void appendTo(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException { 58 | ReflectionUtil.replaceFieldValue(ClassLoader.class, "parent", classLoader, isolatedClassLoader, true); 59 | } 60 | 61 | public AppendableClassLoader getAssemblyClassLoader() { 62 | return assemblyClassLoader; 63 | } 64 | 65 | public IsolatedClassLoader getIsolatedClassLoader() { 66 | return isolatedClassLoader; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/runtime/loader/AppendableClassLoader.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.runtime.loader; 2 | 3 | import java.net.URL; 4 | import java.net.URLClassLoader; 5 | 6 | /** 7 | * 可以直接添加 URL 的classloader 8 | */ 9 | public class AppendableClassLoader extends URLClassLoader { 10 | public AppendableClassLoader(ClassLoader parent) { 11 | this(new URL[0], parent); 12 | } 13 | 14 | public AppendableClassLoader(URL[] urls, ClassLoader parent) { 15 | super(urls, parent); 16 | } 17 | 18 | @Override 19 | public void addURL(URL url) { 20 | super.addURL(url); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/runtime/loader/IsolatedClassLoader.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.runtime.loader; 2 | 3 | import java.net.URL; 4 | import java.net.URLClassLoader; 5 | import java.util.Arrays; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | *

插件自定义的加载器,用于隔离依赖

13 | *

本ClassLoader将优先加载urls中有的class,而不是双亲委托

14 | */ 15 | public class IsolatedClassLoader extends URLClassLoader { 16 | /** 17 | * 自由添加黑名单 18 | */ 19 | public static final Set BLACK_LIST = new HashSet<>(); 20 | 21 | public IsolatedClassLoader(ClassLoader parent) { 22 | this(new URL[0], parent); 23 | } 24 | 25 | public IsolatedClassLoader(URL[] urls, ClassLoader parent) { 26 | super(urls, parent); 27 | } 28 | 29 | 30 | public static void addBlackList(Class clazz) { 31 | BLACK_LIST.add(clazz.getName()); 32 | List subClasses = Arrays.stream(clazz.getDeclaredClasses()).map(Class::getName).collect(Collectors.toList()); 33 | BLACK_LIST.addAll(subClasses); 34 | } 35 | 36 | @Override 37 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 38 | synchronized (getClassLoadingLock(name)) { 39 | Class loadedClass = findLoadedClass(name); 40 | if (loadedClass == null) { 41 | String path = name.replace('.', '/').concat(".class"); 42 | URL resource = findResource(path); 43 | // 读取依赖 44 | if (resource != null && !BLACK_LIST.contains(name)) { 45 | try { 46 | loadedClass = findClass(name); 47 | } catch (ClassNotFoundException ignored) { 48 | } 49 | } 50 | // 不是依赖 51 | if (loadedClass == null) { 52 | ClassLoader parent = getParent(); 53 | if (parent != null) { 54 | loadedClass = parent.loadClass(name); 55 | } 56 | } 57 | } 58 | if (resolve) { 59 | resolveClass(loadedClass); 60 | } 61 | return loadedClass; 62 | } 63 | } 64 | 65 | @Override 66 | public void addURL(URL url) { 67 | super.addURL(url); 68 | } 69 | } -------------------------------------------------------------------------------- /core/src/main/java/top/iseason/bukkittemplate/utils/JavaVersion.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.utils; 2 | 3 | public class JavaVersion { 4 | private static int version; 5 | 6 | static { 7 | String versionString = System.getProperty("java.version"); 8 | int indexOf = versionString.indexOf('.'); 9 | try { 10 | if (indexOf > 0) { 11 | String substring = versionString.substring(0, indexOf); 12 | if (substring.equals("1")) { 13 | int indexOf1 = versionString.indexOf('.', indexOf + 1); 14 | version = Integer.parseInt(versionString.substring(indexOf + 1, indexOf1)); 15 | } else { 16 | version = Integer.parseInt(substring); 17 | } 18 | } else { 19 | version = Integer.parseInt(versionString); 20 | } 21 | } catch (Exception e) { 22 | version = 8; 23 | } 24 | } 25 | 26 | public static boolean isGreaterOrEqual(int version) { 27 | return JavaVersion.version >= version; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/command/CommandNodeExecutor.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.command 2 | 3 | import org.bukkit.command.CommandSender 4 | import java.util.function.BiConsumer 5 | 6 | fun interface CommandNodeExecutor : BiConsumer -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/command/ParmaException.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.command 2 | 3 | /** 4 | * 参数异常,用于传递消息 5 | */ 6 | class ParmaException(val arg: String, val paramAdopter: ParamAdopter<*>? = null) : Exception(arg) -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/command/Parmas.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.command 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.Material 5 | import org.bukkit.command.CommandSender 6 | import org.bukkit.potion.PotionEffectType 7 | 8 | /** 9 | * 一个命令节点的参数 10 | */ 11 | class Params(val params: Array, val node: CommandNode) { 12 | 13 | var readIndex = 0 14 | 15 | /** 16 | * 获取指定参数类型的参数,不存在返回null 17 | * @param index 参数的位置 18 | */ 19 | inline fun getOptionalParam(index: Int): T? { 20 | val param = params.getOrNull(index) ?: return null 21 | return ParamAdopter.getOptionalTypedParam(T::class, param) 22 | } 23 | 24 | /** 25 | * 获取指定参数类型的参数 26 | * @param index 参数的位置 27 | */ 28 | inline fun getParam(index: Int): T { 29 | val param = params.getOrNull(index) 30 | ?: throw ParmaException("&c参数 &6${node.params.getOrNull(index)?.placeholder ?: "位置 $index"} &c不存在!") 31 | return ParamAdopter.getTypedParam(T::class, param) 32 | } 33 | 34 | /** 35 | * 获取下一个参数 36 | */ 37 | inline fun next() = getParam(readIndex).also { readIndex++ } 38 | 39 | /** 40 | * 获取下一个可选参数 41 | */ 42 | inline fun nextOrNull() = getOptionalParam(readIndex)?.also { readIndex++ } 43 | inline fun nextOrDefault(default: T) = getOptionalParam(readIndex)?.also { readIndex++ } ?: default 44 | 45 | /** 46 | * 判断是否存在某个参数 47 | */ 48 | fun hasParma(str: String, ignoreCase: Boolean = true) = params.any { it.equals(str, ignoreCase) } 49 | } 50 | 51 | /** 52 | * 命令参数 53 | */ 54 | open class Param( 55 | /** 56 | * 占位符 57 | */ 58 | val placeholder: String, 59 | /** 60 | * 参数建议,存在运行时建议时不会使用 61 | */ 62 | var suggest: Collection? = null, 63 | /** 64 | * 参数建议,运行时生成的建议,优先级高于 suggest 65 | */ 66 | var suggestRuntime: RuntimeSuggestParams? = null 67 | ) { 68 | fun interface RuntimeSuggestParams { 69 | fun getParams(sender: CommandSender): Collection 70 | } 71 | } 72 | 73 | /** 74 | * 参数建议缓存,避免无所谓的内存消耗 75 | */ 76 | object ParamSuggestCache { 77 | /** 78 | * 建议在线玩家名称 79 | */ 80 | val playerParam: Param.RuntimeSuggestParams = 81 | Param.RuntimeSuggestParams { Bukkit.getOnlinePlayers().map { p -> p.name } } 82 | 83 | /** 84 | * 建议药水效果名 85 | */ 86 | val potionTypes = PotionEffectType.values().filterNotNull().map { 87 | it.name.lowercase() 88 | } 89 | 90 | /** 91 | * 建议物品材质名 92 | */ 93 | val materialTypes = Material.entries.map { 94 | it.name.lowercase() 95 | } 96 | 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/ConfigKey.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config 2 | 3 | import java.lang.reflect.Field 4 | 5 | /** 6 | * 配置中的一个Key,用于储存反射到的Field,方便修改值 7 | */ 8 | internal class ConfigKey(val key: String, val field: Field, val comments: List?) { 9 | init { 10 | field.isAccessible = true 11 | } 12 | 13 | /** 14 | * 设置Field中的值 15 | */ 16 | fun setValue(parent: Any, value: Any) { 17 | field.set(parent, value) 18 | } 19 | 20 | /** 21 | * 读取Field中的值 22 | */ 23 | fun getValue(parent: Any): Any? { 24 | return field.get(parent) 25 | } 26 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/ConfigWatcher.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config 2 | 3 | import com.sun.nio.file.SensitivityWatchEventModifier 4 | import org.bukkit.scheduler.BukkitRunnable 5 | import top.iseason.bukkittemplate.BukkitTemplate 6 | import top.iseason.bukkittemplate.DisableHook 7 | import java.io.File 8 | import java.io.IOException 9 | import java.nio.file.Files 10 | import java.nio.file.Path 11 | import java.nio.file.StandardWatchEventKinds 12 | import java.nio.file.WatchService 13 | 14 | /** 15 | * 配置文件监听器,负责实现配置修改之后的自动重载 16 | */ 17 | class ConfigWatcher private constructor(private val folder: Path) : BukkitRunnable() { 18 | 19 | private val service: WatchService = run { 20 | val path = if (Files.isSymbolicLink(folder)) { 21 | Files.readSymbolicLink(folder) 22 | } else folder 23 | val newWatchService = path.fileSystem.newWatchService() 24 | try { 25 | path.register( 26 | newWatchService, 27 | arrayOf(StandardWatchEventKinds.ENTRY_MODIFY), 28 | SensitivityWatchEventModifier.HIGH, 29 | ) 30 | } catch (_: Exception) { 31 | //报错说明被注册过了,不再注册 32 | } 33 | newWatchService 34 | } 35 | 36 | /** 37 | * 自动重载的实现方法 38 | */ 39 | override fun run() { 40 | val key = try { 41 | service.poll() ?: return 42 | } catch (e: Exception) { 43 | cancel() 44 | return 45 | } 46 | //监听文件修改 47 | for (pollEvent in key.pollEvents()) { 48 | if (pollEvent.kind() != StandardWatchEventKinds.ENTRY_MODIFY) continue 49 | val path = pollEvent.context() as Path 50 | if (!path.toString().endsWith(".yml")) break 51 | val absolutePath = "${folder}${File.separatorChar}$path" 52 | val simpleYAMLConfig = SimpleYAMLConfig.configs[absolutePath] ?: continue 53 | if (simpleYAMLConfig.isAutoUpdate) { 54 | simpleYAMLConfig.load() 55 | break 56 | } 57 | } 58 | key.reset() 59 | } 60 | 61 | /** 62 | * 取消本监听器 63 | */ 64 | override fun cancel() { 65 | super.cancel() 66 | service.close() 67 | } 68 | 69 | companion object { 70 | private val folders = hashMapOf() 71 | 72 | init { 73 | DisableHook.addTask { 74 | onDisable() 75 | } 76 | } 77 | 78 | /** 79 | * 监听某个文件夹,如果存在则返回该监听器 80 | */ 81 | @Throws(IOException::class) 82 | fun fromFile(file: File): ConfigWatcher { 83 | val parentFile = file.absoluteFile.parentFile 84 | val folder = parentFile.toString() 85 | val existWatcher = folders[folder] 86 | if (existWatcher != null) return existWatcher 87 | val configWatcher = ConfigWatcher(parentFile.toPath()) 88 | folders[folder] = configWatcher 89 | configWatcher.runTaskTimerAsynchronously(BukkitTemplate.getPlugin(), 20L + (folders.size % 5) * 4, 20L) 90 | return configWatcher 91 | } 92 | 93 | private fun onDisable() { 94 | for (v in folders.values) { 95 | try { 96 | v.cancel() 97 | } catch (e: Exception) { 98 | e.printStackTrace() 99 | } 100 | } 101 | folders.clear() 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/Lang.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config 2 | 3 | import org.bukkit.configuration.ConfigurationSection 4 | import org.bukkit.configuration.MemorySection 5 | import top.iseason.bukkittemplate.config.annotations.Comment 6 | import top.iseason.bukkittemplate.debug.SimpleLogger 7 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils 8 | 9 | open class Lang( 10 | defaultPath: String? = null, 11 | isAutoUpdate: Boolean = true, 12 | updateNotify: Boolean = true 13 | ) : SimpleYAMLConfig(defaultPath, isAutoUpdate, updateNotify) { 14 | 15 | @Comment( 16 | "", 17 | "消息留空将不会显示,使用 '\\n' 或换行符 可以换行", 18 | "支持 & 颜色符号,1.17以上支持16进制颜色代码,如 #66ccff", 19 | "{0}、{1}、{2}、{3} 等格式为该消息独有的变量占位符", 20 | "所有消息支持PlaceHolderAPI", 21 | "以下是一些特殊消息, 大小写不敏感,可以通过 \\n 自由组合", 22 | "以 [Broadcast] 开头将以广播的形式发送,支持BungeeCord", 23 | "以 [Actionbar] 开头将发送ActionBar消息", 24 | "以 [Title] 开头将发送标题消息,格式为 大标题\\n小标题", 25 | "以 [Command] 开头将以消息接收者的身份运行命令", 26 | "以 [Console] 开头将以控制台的身份运行命令", 27 | "以 [OP-Command] 开头将赋予消息接收者临时op运行命令 (慎用)" 28 | ) 29 | var readme = "" 30 | 31 | @Comment("", "系统消息设置") 32 | private var system: MemorySection? = null 33 | 34 | @Comment("", "消息前缀") 35 | private var system__msg_prefix = MessageUtils.defaultPrefix 36 | 37 | @Comment("", "控制台消息前缀") 38 | private var system__log_prefix = MessageUtils.defaultPrefix 39 | 40 | @Comment( 41 | "", "是否使用 MiniMessage 模式, 同时不支持&符号, 第一次开启将会自动下载依赖", 42 | "格式: https://docs.advntr.dev/minimessage/format.html", 43 | "网页可视化: https://webui.advntr.dev/" 44 | ) 45 | private var system__mini_message = false 46 | 47 | override fun onLoaded(section: ConfigurationSection) { 48 | if (system__mini_message) MessageUtils.enableMiniMessage() 49 | else MessageUtils.disableMiniMessage() 50 | MessageUtils.defaultPrefix = system__msg_prefix 51 | SimpleLogger.prefix = system__log_prefix 52 | } 53 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/annotations/Comment.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.annotations 2 | 3 | /** 4 | * 添加注释,只在SimpleYAMLConfig中有效 5 | */ 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @Repeatable 8 | @Target(AnnotationTarget.FIELD) 9 | annotation class Comment(vararg val value: String) -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/annotations/FilePath.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.annotations 2 | 3 | /** 4 | * 指定配置文件路径,只在SimpleYAMLConfig中有效 5 | */ 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @Target(AnnotationTarget.CLASS) 8 | annotation class FilePath(val path: String = "config.yml") -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/annotations/Key.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.annotations 2 | 3 | /** 4 | * 指定配置键值,如果声明在类上则默认该类所有可变成员都为键,只在SimpleYAMLConfig中有效 5 | */ 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @Target(AnnotationTarget.FIELD, AnnotationTarget.CLASS) 8 | annotation class Key(val key: String = "") 9 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/ConfigType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import java.lang.reflect.Field 4 | 5 | /** 6 | * SimpleYAMLConfig 的对象转换器 7 | */ 8 | interface ConfigType { 9 | 10 | /** 11 | * 该对象是否符合该类型 12 | * @param clazz Field 的类型 13 | */ 14 | fun checkType(clazz: Class<*>): Boolean 15 | 16 | /** 17 | * 从config对象中读取,返回值将写入Field中 18 | * @param obj config.get 返回值 19 | * @param clazz Field 的类型 20 | */ 21 | fun read(obj: Any, field: Field): Any 22 | 23 | /** 24 | * 保存到配置中,返回值将直接调用 config.set 写入配置中J 25 | * @param obj Field 的值 26 | */ 27 | fun save(obj: Any): Any 28 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/DefaultConfigType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import java.lang.reflect.Field 4 | 5 | internal object DefaultConfigType : ConfigType { 6 | /** 7 | * 所有配置类型 8 | */ 9 | val types = mutableListOf() 10 | 11 | //默认类型 12 | init { 13 | types.add(SetType) 14 | types.add(ListType) 15 | types.add(MapType) 16 | // types.add(EnumSetType) 17 | } 18 | 19 | fun matchType(clazz: Class<*>): ConfigType { 20 | return types.findLast { it.checkType(clazz) } ?: DefaultConfigType 21 | } 22 | 23 | override fun checkType(clazz: Class<*>): Boolean { 24 | return true 25 | } 26 | 27 | override fun read(obj: Any, field: Field): Any { 28 | return obj 29 | } 30 | 31 | override fun save(obj: Any): Any { 32 | return obj 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/EnumSetType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import java.lang.reflect.Field 4 | import java.lang.reflect.ParameterizedType 5 | import java.util.* 6 | 7 | /** 8 | * EnumSet类型 9 | */ 10 | object EnumSetType : ConfigType { 11 | override fun checkType(clazz: Class<*>): Boolean { 12 | return EnumSet::class.java.isAssignableFrom(clazz) 13 | } 14 | 15 | override fun read(obj: Any, field: Field): Any { 16 | val clazz = field.type 17 | if (EnumSet::class.java.isAssignableFrom(clazz)) { 18 | val enumClass = getEnumSetClass(field) 19 | val noneOf = EnumSet.noneOf(enumClass) 20 | if (enumClass != null) { 21 | val mapNotNull = (obj as Collection<*>).mapNotNull { 22 | try { 23 | java.lang.Enum.valueOf( 24 | enumClass, it.toString() 25 | .trim() 26 | .uppercase() 27 | .replace(' ', '_') 28 | .replace('-', '_') 29 | ) 30 | } catch (e: Exception) { 31 | null 32 | } 33 | } 34 | noneOf.addAll(mapNotNull as Collection) 35 | return noneOf 36 | } 37 | } 38 | return obj 39 | } 40 | 41 | override fun save(obj: Any): Any { 42 | return (obj as EnumSet<*>).map { it.name } 43 | } 44 | 45 | private fun getEnumSetClass(field: Field): Class>? { 46 | val genericType = field.genericType 47 | if (genericType is ParameterizedType) { 48 | val actualTypeArguments = genericType.actualTypeArguments 49 | if (actualTypeArguments.isNotEmpty() && actualTypeArguments[0] is Class<*>) { 50 | val enumClass = actualTypeArguments[0] as? Class> 51 | if (enumClass != null && enumClass.isEnum) { 52 | return enumClass 53 | } 54 | } 55 | } 56 | return null 57 | } 58 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/EnumType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import java.lang.reflect.Field 4 | 5 | /** 6 | * 枚举类型 7 | */ 8 | object EnumType : ConfigType { 9 | override fun checkType(clazz: Class<*>): Boolean { 10 | return Enum::class.java.isAssignableFrom(clazz) 11 | } 12 | 13 | override fun read(obj: Any, field: Field): Any { 14 | return try { 15 | java.lang.Enum.valueOf( 16 | field.type as Class>, 17 | obj.toString().trim().uppercase() 18 | .replace(' ', '_') 19 | .replace('-', '_') 20 | ) 21 | } catch (e: Exception) { 22 | obj 23 | } 24 | } 25 | 26 | override fun save(obj: Any): Any { 27 | return obj.toString() 28 | } 29 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/ListType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import java.lang.reflect.Field 4 | import java.util.* 5 | import java.util.concurrent.CopyOnWriteArrayList 6 | 7 | /** 8 | * 一般List : CopyOnWriteArrayList、LinkedList、ArrayList、Stack 9 | */ 10 | object ListType : ConfigType { 11 | override fun checkType(clazz: Class<*>): Boolean { 12 | return List::class.java.isAssignableFrom(clazz) 13 | } 14 | 15 | override fun read(obj: Any, field: Field): Any { 16 | val clazz = field.type 17 | val list = obj as List<*> 18 | if (CopyOnWriteArrayList::class.java.isAssignableFrom(clazz)) { 19 | return CopyOnWriteArrayList(list) 20 | } 21 | if (LinkedList::class.java.isAssignableFrom(clazz)) { 22 | return LinkedList(list) 23 | } 24 | if (ArrayList::class.java.isAssignableFrom(clazz)) { 25 | return ArrayList(list) 26 | } 27 | if (Stack::class.java.isAssignableFrom(clazz)) { 28 | return Stack().apply { 29 | addAll(list) 30 | } 31 | } 32 | return list 33 | } 34 | 35 | override fun save(obj: Any): Any { 36 | return obj 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/MapType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import org.bukkit.configuration.MemorySection 4 | import java.lang.reflect.Field 5 | import java.util.concurrent.ConcurrentHashMap 6 | 7 | /** 8 | * 一般Map、ConcurrentHashMap、LinkedHashMap、HashMap 9 | */ 10 | object MapType : ConfigType { 11 | 12 | override fun checkType(clazz: Class<*>): Boolean { 13 | return Map::class.java.isAssignableFrom(clazz) 14 | } 15 | 16 | override fun read(obj: Any, field: Field): Any { 17 | val clazz = field.type 18 | val values = (obj as MemorySection).getValues(true) 19 | if (ConcurrentHashMap::class.java.isAssignableFrom(clazz)) { 20 | return ConcurrentHashMap(values) 21 | } 22 | if (LinkedHashMap::class.java.isAssignableFrom(clazz)) { 23 | return LinkedHashMap(values) 24 | } 25 | if (HashMap::class.java.isAssignableFrom(clazz)) { 26 | return HashMap(values) 27 | } 28 | return values 29 | } 30 | 31 | override fun save(obj: Any): Any { 32 | return obj 33 | } 34 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/config/type/SetType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.config.type 2 | 3 | import java.lang.reflect.Field 4 | import java.util.concurrent.ConcurrentHashMap 5 | import java.util.concurrent.CopyOnWriteArraySet 6 | 7 | /** 8 | * 一般Set CopyOnWriteArraySet、ConcurrentHashMap.KeySetView、LinkedHashSet、HashSet、 9 | */ 10 | object SetType : ConfigType { 11 | override fun checkType(clazz: Class<*>): Boolean { 12 | return Set::class.java.isAssignableFrom(clazz) 13 | } 14 | 15 | override fun read(obj: Any, field: Field): Any { 16 | val clazz = field.type 17 | val set = obj as Collection<*> 18 | if (CopyOnWriteArraySet::class.java.isAssignableFrom(clazz)) { 19 | return CopyOnWriteArraySet(set) 20 | } 21 | if (ConcurrentHashMap.KeySetView::class.java.isAssignableFrom(clazz)) { 22 | val newKeySet = ConcurrentHashMap.newKeySet(set.size) 23 | newKeySet.addAll(set) 24 | return newKeySet 25 | } 26 | if (LinkedHashSet::class.java.isAssignableFrom(clazz)) { 27 | return LinkedHashSet(set) 28 | } 29 | if (HashSet::class.java.isAssignableFrom(clazz)) { 30 | return HashSet(set) 31 | } 32 | return set.toHashSet() 33 | } 34 | 35 | override fun save(obj: Any): Any { 36 | return (obj as Collection<*>).toList() 37 | } 38 | 39 | 40 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/debug/SimpleLogger.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.debug 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.ChatColor 5 | import top.iseason.bukkittemplate.BukkitTemplate 6 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 7 | 8 | /** 9 | * 输出日志 10 | */ 11 | fun info(message: Any?) { 12 | Bukkit.getConsoleSender().sendColorMessage(message, SimpleLogger.prefix) 13 | } 14 | 15 | /** 16 | * 输出debug日志,只有当 SimpleLogger.isDebug 为true 时输出 17 | */ 18 | inline fun debug(message: () -> String?) { 19 | if (SimpleLogger.isDebug) { 20 | debug(message()) 21 | } 22 | } 23 | 24 | fun debug(message: String?) = Bukkit.getConsoleSender() 25 | .sendColorMessage("${ChatColor.GRAY}[DEBUG] $message", SimpleLogger.prefix) 26 | 27 | inline fun onDebug(action: () -> Unit) { 28 | if (SimpleLogger.isDebug) 29 | action() 30 | } 31 | 32 | /** 33 | * 输出警告日志 34 | */ 35 | fun warn(message: Any?) { 36 | BukkitTemplate.getPlugin().logger.warning(message.toString()) 37 | } 38 | 39 | /** 40 | * 日志配置类 41 | */ 42 | object SimpleLogger { 43 | /** 44 | * 消息前缀 45 | */ 46 | var prefix = "&a[&6${BukkitTemplate.getPlugin().description.name}&a] &f" 47 | 48 | /** 49 | * 是否是debug模式 50 | */ 51 | var isDebug = false 52 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/hook/BaseHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.hook 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.plugin.Plugin 5 | import top.iseason.bukkittemplate.debug.info 6 | 7 | /** 8 | * 软依赖钩子,需要传入依赖的名字 9 | */ 10 | abstract class BaseHook(val pluginName: String) { 11 | 12 | private var plugin: Plugin? = null 13 | 14 | /** 15 | * 获取插件入口 16 | */ 17 | fun getInstance(): Plugin { 18 | check(plugin != null) { "依赖 $pluginName 不存在, 你应该使用 hasHooked 方法检查一下再调用!" } 19 | return plugin!! 20 | } 21 | 22 | /** 23 | * 检查是否存在软依赖 24 | */ 25 | val hasHooked get() = plugin != null 26 | 27 | /** 28 | * 检查软依赖是否存在 29 | */ 30 | open fun checkHooked() { 31 | plugin = Bukkit.getPluginManager().getPlugin(pluginName) 32 | if (plugin != null) 33 | info("&a检测到兼容插件: &6$pluginName") 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/hook/PlaceHolderHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.hook 2 | 3 | import me.clip.placeholderapi.PlaceholderAPI 4 | import org.bukkit.Bukkit 5 | import org.bukkit.OfflinePlayer 6 | import top.iseason.bukkittemplate.BukkitTemplate 7 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.toColor 8 | 9 | object PlaceHolderHook : BaseHook("PlaceholderAPI") { 10 | init { 11 | Bukkit.getScheduler().runTaskAsynchronously(BukkitTemplate.getPlugin(), PlaceHolderHook::checkHooked) 12 | } 13 | 14 | fun setPlaceHolder(str: String, player: OfflinePlayer?): String { 15 | return if (hasHooked) 16 | PlaceholderAPI.setPlaceholders(player, str).toColor() 17 | else str.toColor() 18 | } 19 | 20 | fun setPlaceHolder(str: List, player: OfflinePlayer?): List { 21 | return if (hasHooked) str.map { PlaceholderAPI.setPlaceholders(player, it).toColor() } 22 | else str.toColor() 23 | } 24 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/UIBuilder.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.inventory.Inventory 5 | import top.iseason.bukkittemplate.ui.container.BaseUI 6 | import top.iseason.bukkittemplate.ui.container.UIContainer 7 | 8 | 9 | /** 10 | * 新建一个UI的对象 11 | */ 12 | inline fun buildUI(builder: T.() -> Unit = {}): Inventory { 13 | return T::class.java.newInstance().apply(builder).build() 14 | } 15 | 16 | /** 17 | * 打开某类UI,必要时可以修改,使用此api请确保该类有空构造函数 18 | */ 19 | inline fun Player.openUI(builder: T.() -> Unit = {}) { 20 | try { 21 | openInventory(buildUI(builder)) 22 | } catch (ex: Throwable) { 23 | ex.printStackTrace() 24 | } 25 | } 26 | 27 | /** 28 | * 打开某类UI,必要时可以修改,使用此api请确保该类有空构造函数 29 | */ 30 | inline fun Player.openPageableUI(builder: T.() -> Unit = {}) { 31 | try { 32 | T::class.java.newInstance().also(builder).openFor(this) 33 | } catch (ex: Throwable) { 34 | ex.printStackTrace() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/container/ChestUI.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.container 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.inventory.Inventory 5 | 6 | /** 7 | * 箱子界面 8 | * @param title 标题 9 | * @param row 行数 10 | */ 11 | open class ChestUI( 12 | var title: String = "Chest UI", 13 | row: Int = 6, 14 | override var clickDelay: Long = 200L 15 | ) : BaseUI(row * 9) { 16 | 17 | override fun reset() { 18 | resetSlots() 19 | } 20 | 21 | override fun buildInventory(): Inventory = Bukkit.createInventory(this, super.size, title) 22 | 23 | override fun clone(): BaseUI { 24 | val chestUI = ChestUI(this.title, this.size / 9, this.clickDelay) 25 | slots.forEachIndexed { index, baseSlot -> 26 | chestUI.slots[index] = baseSlot?.clone(index) 27 | } 28 | return chestUI.also { 29 | it.lockOnBottom = lockOnBottom 30 | it.lockOnTop = lockOnTop 31 | it.async = async 32 | it.onClick = onClick 33 | it.onClicked = onClicked 34 | it.onOpen = onOpen 35 | it.onClose = onClose 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/container/LazyUIContainer.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.container 2 | 3 | import org.bukkit.entity.HumanEntity 4 | 5 | /** 6 | * 多页UI,采用懒加载模式,当UI显示时才初始化 7 | */ 8 | open class LazyUIContainer( 9 | protected val pageTypes: Array>, 10 | /** 11 | * 是否启用缓存,如果为false则每人的每个UI都是新对象 12 | */ 13 | protected val allowCache: Boolean = true 14 | ) : UIContainer(arrayOfNulls(pageTypes.size)) { 15 | 16 | /** 17 | * 当页面被第一次加载时,将由class生成对象的方法 18 | */ 19 | open fun onInit(clazz: Class): BaseUI { 20 | return clazz.newInstance() 21 | } 22 | 23 | /** 24 | * 当新的UI被构建前,可以对UI进行修改的回调函数 25 | */ 26 | open fun onUIBuild(ui: BaseUI, player: HumanEntity) { 27 | 28 | } 29 | 30 | /** 31 | * 获取当前的UI,如果不存在则创建一个 32 | */ 33 | override fun getCurrentPage(player: HumanEntity): BaseUI { 34 | val index = viewers[player] ?: 0 35 | if (!allowCache || pages[index] == null) { 36 | val ui = onInit(pageTypes[index]) 37 | onUIBuild(ui, player) 38 | ui.build() 39 | ui.container = this 40 | pages[index] = ui 41 | } 42 | return super.getCurrentPage(player)!! 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/container/Pageable.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.container 2 | 3 | interface Pageable { 4 | var container: UIContainer? 5 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/container/UIContainer.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.container 2 | 3 | import org.bukkit.entity.HumanEntity 4 | import top.iseason.bukkittemplate.utils.other.submit 5 | 6 | /** 7 | * 多页UI 8 | */ 9 | @Suppress("unused") 10 | open class UIContainer( 11 | /** 12 | * 储存多页数组 13 | */ 14 | protected val pages: Array 15 | ) { 16 | 17 | // 页码 18 | val size = pages.size 19 | protected val viewers = mutableMapOf() 20 | 21 | /** 22 | * 翻页时调用 23 | * @param from 源页码 24 | * @param to 目标页码 25 | */ 26 | open var onPageChanged: ((from: Int, to: Int) -> Unit)? = null 27 | 28 | /** 29 | * 获取当前页码的UI 30 | */ 31 | open fun getCurrentPage(player: HumanEntity): BaseUI? { 32 | val index = viewers[player] ?: 0 33 | val ui = pages[index] ?: return null 34 | ui.container = this 35 | if (!ui.hasBuilt) { 36 | ui.build() 37 | } 38 | return ui 39 | } 40 | 41 | /** 42 | * 定位玩家视图到下一页 43 | */ 44 | open fun nextPage(player: HumanEntity) { 45 | val next = ((viewers[player] ?: 0) + 1) % size 46 | val inventory = setPage(next, player)?.inventory ?: return 47 | submit { 48 | player.openInventory(inventory) 49 | } 50 | } 51 | 52 | /** 53 | * 定位玩家视图到上一页 54 | */ 55 | open fun lastPage(player: HumanEntity) { 56 | var last = (viewers[player] ?: 0) - 1 57 | if (last < 0) last += size 58 | val inventory = setPage(last, player)?.inventory ?: return 59 | submit { 60 | player.openInventory(inventory) 61 | } 62 | } 63 | 64 | /** 65 | * 定位到第 page 页 66 | */ 67 | open fun setPage(page: Int, player: HumanEntity): BaseUI? { 68 | require(page in 0..size) { "page $page is not exist!" } 69 | onPageChanged?.invoke(viewers[player] ?: 0, page) 70 | viewers[player] = page 71 | return getCurrentPage(player) 72 | } 73 | 74 | /** 75 | * 为某个玩家打开UI 76 | */ 77 | open fun openFor(player: HumanEntity) { 78 | require(pages.isNotEmpty()) { "Your pageable ui must possess at lease 1 page" } 79 | val currentPage = getCurrentPage(player) ?: return 80 | submit { 81 | player.openInventory(currentPage.inventory) 82 | } 83 | } 84 | 85 | /** 86 | * 复制 87 | */ 88 | open fun clone(): UIContainer { 89 | val copyOf = pages.copyOf() 90 | copyOf.forEachIndexed { index, ui -> 91 | if (ui == null) return@forEachIndexed 92 | copyOf[index] = ui.clone() 93 | } 94 | return UIContainer(copyOf).also { it.onPageChanged = onPageChanged } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/slot/BaseSlot.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.slot 2 | 3 | import org.bukkit.inventory.Inventory 4 | import org.bukkit.inventory.ItemStack 5 | import top.iseason.bukkittemplate.ui.container.BaseUI 6 | import top.iseason.bukkittemplate.ui.container.UIContainer 7 | 8 | /** 9 | * 一个物品槽 10 | */ 11 | interface BaseSlot { 12 | /** 13 | * 物品槽的位置 14 | */ 15 | val index: Int 16 | 17 | /** 18 | * 存在的物品栏 19 | */ 20 | var baseInventory: Inventory? 21 | 22 | 23 | /** 24 | * 存在的物品 25 | */ 26 | var itemStack: ItemStack? 27 | 28 | /** 29 | * 复制Slot到指定Index 30 | */ 31 | fun clone(index: Int): BaseSlot 32 | 33 | /** 34 | * 重置Slot 35 | */ 36 | fun reset() 37 | 38 | 39 | } 40 | 41 | /** 42 | * 获取slot的ui 43 | */ 44 | fun T.getUI(): BaseUI = baseInventory!!.holder as BaseUI 45 | 46 | /** 47 | * 获取slot的container 48 | */ 49 | fun T.getContainer(): UIContainer? = getUI().container 50 | 51 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/slot/Button.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.slot 2 | 3 | import org.bukkit.Material 4 | import org.bukkit.inventory.ItemStack 5 | import org.bukkit.inventory.meta.ItemMeta 6 | 7 | /** 8 | * 可点击的按钮 9 | * @param index 位于容器中的位置 10 | */ 11 | @Suppress("MemberVisibilityCanBePrivate", "unused") 12 | open class Button( 13 | override var rawItemStack: ItemStack?, 14 | index: Int = 0, 15 | ) : ClickSlot(rawItemStack, index) { 16 | var itemMeta: ItemMeta 17 | get() = 18 | itemStack!!.itemMeta!! 19 | set(value) { 20 | itemStack!!.itemMeta = value 21 | } 22 | 23 | /** 24 | * 显示的图标 25 | */ 26 | var material: Material 27 | get() = 28 | itemStack!!.type 29 | set(value) { 30 | itemStack!!.type = value 31 | } 32 | 33 | /** 34 | * 显示的名称 35 | */ 36 | var displayName: String 37 | get() = itemMeta.displayName 38 | set(value) { 39 | itemMeta = itemMeta.apply { setDisplayName(value) } 40 | } 41 | 42 | /** 43 | * 显示的lore 44 | */ 45 | var lore: List 46 | get() = itemMeta.lore ?: emptyList() 47 | set(value) { 48 | itemMeta = itemMeta.apply { lore = value } 49 | } 50 | 51 | override fun reset() { 52 | itemStack = rawItemStack 53 | } 54 | 55 | override fun clone(index: Int): Button = Button(rawItemStack, index).also { 56 | it.baseInventory = baseInventory 57 | it.onClick = onClick 58 | it.onClicked = onClicked 59 | it.asyncClick = asyncClick 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/slot/ClickSlot.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.slot 2 | 3 | import org.bukkit.event.inventory.InventoryClickEvent 4 | import org.bukkit.inventory.Inventory 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkittemplate.ui.container.BaseUI 7 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 8 | 9 | 10 | /** 11 | * 可点击的Slot 12 | */ 13 | abstract class ClickSlot( 14 | /** 15 | * 按钮对应的默认图标 16 | */ 17 | open var rawItemStack: ItemStack? = null, 18 | /** 19 | * 位于容器中的位置 20 | */ 21 | override val index: Int = 0 22 | 23 | ) : BaseSlot { 24 | 25 | /** 26 | * 与Inventory的ItemStack同步 27 | */ 28 | override var itemStack: ItemStack? 29 | set(value) { 30 | baseInventory?.setItem(index, value) 31 | } 32 | get() { 33 | val item = baseInventory?.getItem(index) 34 | return if (item.checkAir()) rawItemStack 35 | else item 36 | } 37 | 38 | /** 39 | * 依托的Inventory 40 | */ 41 | override var baseInventory: Inventory? = null 42 | 43 | /** 44 | * 点击之前 45 | */ 46 | var onClick: (ClickSlot.(InventoryClickEvent) -> Unit)? = null 47 | 48 | /** 49 | * 点击之后 50 | */ 51 | var onClicked: (ClickSlot.(InventoryClickEvent) -> Unit)? = null 52 | 53 | /** 54 | * 是否异步点击 只对 onClicked 有效 55 | */ 56 | var asyncClick = false 57 | } 58 | 59 | /** 60 | * 点击之后 61 | */ 62 | fun T.onClicked(async: Boolean = false, action: (ClickSlot.(InventoryClickEvent) -> Unit)? = null): T { 63 | this.asyncClick = async 64 | this.onClicked = action 65 | return this 66 | } 67 | 68 | /** 69 | * 点击之前 70 | */ 71 | fun T.onClick(action: (ClickSlot.(InventoryClickEvent) -> Unit)? = null): T { 72 | this.onClick = action 73 | return this 74 | } 75 | 76 | /** 77 | * 设置依托的UI 78 | */ 79 | fun Collection.setUI(ui: BaseUI) { 80 | ui.addSlots(*this.toTypedArray()) 81 | } 82 | 83 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/ui/slot/Icon.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.ui.slot 2 | 3 | import org.bukkit.Material 4 | import org.bukkit.inventory.Inventory 5 | import org.bukkit.inventory.ItemStack 6 | import org.bukkit.inventory.meta.ItemMeta 7 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 8 | 9 | open class Icon( 10 | var rawItemStack: ItemStack, 11 | override val index: Int 12 | 13 | ) : BaseSlot { 14 | override var baseInventory: Inventory? = null 15 | 16 | /** 17 | * 与Inventory的ItemStack同步 18 | */ 19 | override var itemStack: ItemStack? 20 | set(value) { 21 | baseInventory?.setItem(index, value) 22 | } 23 | get() { 24 | val item = baseInventory?.getItem(index) 25 | return if (item.checkAir()) rawItemStack 26 | else item 27 | } 28 | 29 | var itemMeta: ItemMeta 30 | get() = 31 | itemStack!!.itemMeta!! 32 | set(value) { 33 | itemStack!!.itemMeta = value 34 | } 35 | 36 | /** 37 | * 显示的图标 38 | */ 39 | var material: Material 40 | get() = 41 | itemStack!!.type 42 | set(value) { 43 | itemStack!!.type = value 44 | } 45 | 46 | /** 47 | * 显示的名称 48 | */ 49 | var displayName: String 50 | get() = itemMeta.displayName 51 | set(value) { 52 | itemMeta = itemMeta.apply { setDisplayName(value) } 53 | } 54 | 55 | /** 56 | * 显示的lore 57 | */ 58 | var lore: List 59 | get() = itemMeta.lore ?: emptyList() 60 | set(value) { 61 | itemMeta = itemMeta.apply { lore = value } 62 | } 63 | 64 | override fun reset() { 65 | itemStack = rawItemStack 66 | } 67 | 68 | override fun clone(index: Int): Icon = Icon(rawItemStack, index).also { 69 | it.baseInventory = baseInventory 70 | } 71 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/bukkit/EntityUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 2 | 3 | package top.iseason.bukkittemplate.utils.bukkit 4 | 5 | import org.bukkit.entity.Entity 6 | import org.bukkit.entity.EntityType 7 | import org.bukkit.entity.Item 8 | import org.bukkit.entity.Player 9 | import org.bukkit.inventory.InventoryHolder 10 | import org.bukkit.inventory.ItemStack 11 | import org.bukkit.inventory.PlayerInventory 12 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 13 | 14 | /** 15 | * bukkit 的实体相关工具 16 | */ 17 | object EntityUtils { 18 | 19 | /** 20 | * 给予有物品栏的对象物品,如果是实体且放不下将会放置到实体脚下 21 | * @param itemStacks 待输入的物品 22 | * 23 | */ 24 | fun InventoryHolder.giveItems(itemStacks: Array) { 25 | giveItems(*itemStacks) 26 | } 27 | 28 | /** 29 | * 给予有物品栏的对象物品,如果是实体且放不下将会放置到实体脚下 30 | * @param itemStacks 待输入的物品 31 | * 32 | */ 33 | fun InventoryHolder.giveItems(itemStacks: Collection) { 34 | giveItems(itemStacks.toTypedArray()) 35 | } 36 | 37 | /** 38 | * 给予有物品栏的对象物品,如果是实体且放不下将会放置到实体脚下 39 | * @param itemStacks 待输入的物品 40 | * 41 | */ 42 | @JvmName("giveItemsVararg") 43 | fun InventoryHolder.giveItems(vararg itemStacks: ItemStack) { 44 | val addItems = inventory.addItem(*itemStacks).values 45 | if (this !is Entity) return 46 | for (addItem in addItems) { 47 | if (addItem == null) continue 48 | val item = world.spawnEntity(location, EntityType.DROPPED_ITEM) as Item 49 | item.itemStack = addItem 50 | } 51 | } 52 | 53 | // @JvmName("giveItemsOrDrop") 54 | fun InventoryHolder.giveItem(itemStack: ItemStack) { 55 | val addItems = inventory.addItem(itemStack).values 56 | if (this !is Entity) return 57 | for (addItem in addItems) { 58 | if (addItem == null) continue 59 | val item = world.spawnEntity(location, EntityType.DROPPED_ITEM) as Item 60 | item.itemStack = addItem 61 | } 62 | } 63 | 64 | /** 65 | * 获取玩家手上拿着的物品,兼容低版本 66 | * @return 没有或者是空气都返回null 67 | */ 68 | fun PlayerInventory.getHeldItem(): ItemStack? { 69 | val item = getItem(heldItemSlot) 70 | if (item.checkAir()) return null 71 | return item 72 | } 73 | 74 | /** 75 | * 获取玩家手上拿着的物品,兼容低版本 76 | * @return 没有或者是空气都返回null 77 | */ 78 | fun Player.getHeldItem(): ItemStack? = inventory.getHeldItem() 79 | 80 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/bukkit/EventUtils.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.utils.bukkit 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.event.Event 5 | import org.bukkit.event.EventPriority 6 | import org.bukkit.event.HandlerList 7 | import org.bukkit.event.Listener 8 | import org.bukkit.plugin.EventExecutor 9 | import top.iseason.bukkittemplate.BukkitTemplate 10 | 11 | /** 12 | * bukkit的事件相关工具 13 | */ 14 | object EventUtils { 15 | /** 16 | * 快速注册监听器 17 | */ 18 | fun Listener.registerListener() { 19 | Bukkit.getPluginManager().registerEvents(this, BukkitTemplate.getPlugin()) 20 | } 21 | 22 | /** 23 | * 快速注销监听器 24 | */ 25 | fun Listener.unregister() { 26 | HandlerList.unregisterAll(this) 27 | } 28 | 29 | /** 30 | * 虚拟的Listener 31 | */ 32 | fun interface FakeEventListener : EventExecutor, Listener 33 | 34 | /** 35 | * 通过方法快速注册一个事件监听器 36 | */ 37 | inline fun listen( 38 | priority: EventPriority = EventPriority.NORMAL, 39 | ignoreCancelled: Boolean = true, 40 | noinline action: T.() -> Unit 41 | ): FakeEventListener { 42 | return createListener(T::class.java, priority, ignoreCancelled, action) 43 | } 44 | 45 | /** 46 | * 根据class创建事件监听器 47 | */ 48 | @Suppress("UNCHECKED_CAST") 49 | fun createListener( 50 | clazz: Class, 51 | priority: EventPriority = EventPriority.NORMAL, 52 | ignoreCancelled: Boolean = true, 53 | action: E.() -> Unit 54 | ): FakeEventListener { 55 | val fakeEventListener = FakeEventListener { _, event -> 56 | if (clazz.isAssignableFrom(event.javaClass)) { 57 | action.invoke(event as E) 58 | } 59 | } 60 | Bukkit.getPluginManager() 61 | .registerEvent( 62 | clazz, fakeEventListener, priority, 63 | fakeEventListener, BukkitTemplate.getPlugin(), ignoreCancelled 64 | ) 65 | return fakeEventListener 66 | } 67 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/bukkit/IOUtils.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.utils.bukkit 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.entity.Player 5 | import org.bukkit.event.inventory.InventoryCloseEvent 6 | import org.bukkit.inventory.Inventory 7 | import org.bukkit.scheduler.BukkitTask 8 | import top.iseason.bukkittemplate.utils.bukkit.EventUtils.listen 9 | import top.iseason.bukkittemplate.utils.bukkit.EventUtils.unregister 10 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.toColor 11 | import top.iseason.bukkittemplate.utils.other.runSync 12 | import top.iseason.bukkittemplate.utils.other.submit 13 | 14 | /** 15 | * bukkit的玩家输入工具 16 | */ 17 | object IOUtils { 18 | /** 19 | * 打开一个界面,让玩家输入物品,当界面关闭时回调方法 20 | * @param inv 打开的界面 21 | * @param async 是否异步运行 22 | * @param timeout 超时时间,单位tick 23 | * @param onFinish 回调方法 24 | */ 25 | fun Player.onItemInput( 26 | inv: Inventory = Bukkit.createInventory(null, 54, "&a请输入物品".toColor()), 27 | async: Boolean = false, 28 | timeout: Long = 12000, 29 | onFinish: (Inventory) -> Unit 30 | ) { 31 | runSync { 32 | openInventory(inv) 33 | } 34 | var task: BukkitTask? = null 35 | var listener: EventUtils.FakeEventListener? = null 36 | listener = listen { 37 | if (inventory != inv) return@listen 38 | submit(async = async) { 39 | onFinish(inv) 40 | } 41 | task?.cancel() 42 | listener?.unregister() 43 | } 44 | task = submit(delay = timeout, async = async) { 45 | if (openInventory.topInventory == inv) { 46 | closeInventory() 47 | } else { 48 | onFinish(inv) 49 | listener.unregister() 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/bukkit/LocationUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 2 | 3 | package top.iseason.bukkittemplate.utils.bukkit 4 | 5 | import org.bukkit.Location 6 | import org.bukkit.util.Vector 7 | import kotlin.math.cos 8 | import kotlin.math.sin 9 | 10 | /** 11 | * bukkit的位置相关工具 12 | */ 13 | object LocationUtils { 14 | /** 15 | * 根据坐标yaw和pith值获取X方向的单位向量 16 | * @return X方向的单位向量 17 | */ 18 | fun Location.getNormalX(): Vector { 19 | val vector = Vector() 20 | val rotX = yaw.toDouble() 21 | //row =0 , pitch = 0 22 | vector.x = cos(Math.toRadians(rotX)) 23 | vector.z = sin(Math.toRadians(rotX)) 24 | return vector 25 | } 26 | 27 | /** 28 | * 根据坐标yaw和pith值获取Y方向的单位向量 29 | * @return Y方向的单位向量 30 | */ 31 | fun Location.getNormalY(): Vector = direction.getCrossProduct(getNormalX()) 32 | 33 | /** 34 | * 根据坐标yaw和pith值获取Z方向的单位向量 35 | * @return Z方向的单位向量,与 getDirection() 方法一致 36 | */ 37 | fun Location.getNormalZ(): Vector = direction 38 | 39 | /** 40 | * 由相对坐标获取世界坐标 41 | */ 42 | fun Location.getRelativeByCoordinate( 43 | x: Double, 44 | y: Double, 45 | z: Double 46 | ): Location { 47 | return clone().apply { 48 | add(getNormalX().multiply(x)) 49 | add(getNormalY().multiply(y)) 50 | add(getNormalZ().multiply(z)) 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/other/CoolDown.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 2 | 3 | package top.iseason.bukkittemplate.utils.other 4 | 5 | import java.util.* 6 | 7 | /** 8 | * 常规HashMap冷却时间表,适用于大多数情况 9 | */ 10 | class CoolDown { 11 | private val coolDownMap: HashMap = HashMap() 12 | 13 | /** 14 | * 检查键值是否在冷却中 15 | * @param key 键值 16 | * @param coolDown 冷却时间 17 | */ 18 | fun check(key: T, coolDown: Long): Boolean { 19 | return EasyCoolDown.checkType(coolDownMap, key, coolDown) 20 | } 21 | 22 | fun remove(key: T) = coolDownMap.remove(key) 23 | } 24 | 25 | /** 26 | * 使用弱引用的WeakHashMap冷却时间表,适用于不重要、且时间短的冷却 27 | */ 28 | class WeakCoolDown { 29 | private val coolDownMap: WeakHashMap = WeakHashMap() 30 | 31 | /** 32 | * 检查键值是否在冷却中 33 | * @param key 键值 34 | * @param coolDown 冷却时间 35 | * @return true 表示在冷却 36 | */ 37 | fun check(key: T, coolDown: Long): Boolean { 38 | return EasyCoolDown.checkType(coolDownMap, key, coolDown) 39 | } 40 | 41 | fun remove(key: T) = coolDownMap.remove(key) 42 | } 43 | 44 | /** 45 | * 对String键的全局冷却,即开即用,弱引用,适用于不太重要的冷却 46 | */ 47 | object EasyCoolDown { 48 | 49 | private val coolDownMap: MutableMap = Collections.synchronizedMap(WeakHashMap()) 50 | 51 | /** 52 | * 检查键值是否在冷却中 53 | * @param obj 键值 54 | * @param coolDown 冷却时间 55 | * @return true 表示在冷却 56 | */ 57 | fun check(obj: Any, coolDown: Long): Boolean { 58 | val key = obj.toString() 59 | return checkType(coolDownMap, key, coolDown) 60 | } 61 | 62 | // 检查某个map中某个键值是否在某个冷却时间内 63 | fun checkType(map: MutableMap, obj: T, coolDown: Long): Boolean { 64 | val lastTime = map[obj] 65 | val current = System.currentTimeMillis() 66 | if (lastTime == null) { 67 | map[obj] = current 68 | return false 69 | } 70 | if (current - lastTime <= coolDown) return true 71 | map[obj] = current 72 | return false 73 | } 74 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/other/LagCatcher.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.utils.other 2 | 3 | import top.iseason.bukkittemplate.debug.warn 4 | 5 | /** 6 | * 性能测试工具 7 | */ 8 | @Suppress("MemberVisibilityCanBePrivate", "unused") 9 | object LagCatcher { 10 | 11 | private val startTimesMap = mutableMapOf() 12 | 13 | /** 14 | * 对任务进行性能测试,输出任务耗时 15 | * 16 | * @param name 任务名称 17 | * @param cycles 循环任务次数 18 | * @param task 任务 19 | */ 20 | fun performanceTest(name: String, cycles: Int, task: () -> Unit) { 21 | start(name) 22 | for (i in 0 until cycles) { 23 | task() 24 | } 25 | end(name) 26 | } 27 | 28 | 29 | /** 30 | * 检测该任务是否超过 checkValue 毫秒, 超过就输出警告信息 31 | * 32 | * @param name 任务名称 33 | * @param checkValue 检测值 34 | * @param task 任务 35 | */ 36 | fun performanceCheck(name: String, checkValue: Int = 10, task: () -> Unit) { 37 | start(name) 38 | 39 | task() 40 | 41 | end(name, checkValue) 42 | } 43 | 44 | private fun start(name: String) { 45 | startTimesMap[name] = System.currentTimeMillis() 46 | } 47 | 48 | private fun end(name: String, checkValue: Int = 0) { 49 | val duration = System.currentTimeMillis() - (startTimesMap[name] ?: error("$name 未开启性能监控")) 50 | startTimesMap.remove(name) 51 | 52 | if (duration >= checkValue) { 53 | warn("任务『$name』耗时 $duration ms") 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/other/NumberUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 2 | 3 | package top.iseason.bukkittemplate.utils.other 4 | 5 | import kotlin.math.abs 6 | 7 | /** 8 | * 数字工具 9 | */ 10 | object NumberUtils { 11 | private val romanMap: MutableMap = hashMapOf( 12 | 'I' to 1, 13 | 'V' to 5, 14 | 'X' to 10, 15 | 'L' to 50, 16 | 'D' to 500, 17 | 'M' to 1000, 18 | ) 19 | 20 | /** 21 | * 整数转罗马数字,最大到3999 22 | */ 23 | fun Int.toRoman(): String { 24 | if (this > 3999) return toString() 25 | val num = abs(this) 26 | val m = arrayOf("", "M", "MM", "MMM") 27 | val c = arrayOf("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM") 28 | val x = arrayOf("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC") 29 | val i = arrayOf("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX") 30 | val sign = if (this >= 0) "" else "- " 31 | return sign + m[num / 1000] + c[num % 1000 / 100] + x[num % 100 / 10] + i[num % 10] 32 | } 33 | 34 | /** 35 | * 罗马数字转整数 36 | */ 37 | fun romanToInt(s: String): Int { 38 | val temp = s.uppercase() 39 | val n = s.length 40 | var num = romanMap[temp[n - 1]]!! 41 | for (i in n - 2 downTo 0) { 42 | if (romanMap[temp[i]]!! >= romanMap[temp[i + 1]]!!) { 43 | num += romanMap[temp[i]]!! 44 | } else { 45 | num -= romanMap[temp[i]]!! 46 | } 47 | } 48 | return num 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/other/RandomUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Description: 获取安全随机数,比普通随机更随机,但用时也会多一些 3 | * @Author: Iseason2000 4 | * @Date: 2022/1/18 下午2:23 5 | * 6 | */ 7 | 8 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 9 | 10 | package top.iseason.bukkittemplate.utils.other 11 | 12 | import java.security.SecureRandom 13 | import kotlin.math.abs 14 | 15 | /** 16 | * 安全随机数工具,比普通随机更随机,但用时也会多一些 17 | */ 18 | object RandomUtils { 19 | private val SECURE_RANDOM = SecureRandom() 20 | 21 | /** 22 | * 获取安全的随机小数(Double),范围[start,end) 默认[0.0D,1.0D) 23 | */ 24 | fun getDouble(start: Double = 0.0, end: Double = 1.0) = start + SECURE_RANDOM.nextDouble() * (end - start) 25 | 26 | /** 27 | * 获取安全的随机整数,范围[start,end] 默认[0,Int.MAX_VALUE) 28 | */ 29 | fun getInteger(start: Int = 0, end: Int = Int.MAX_VALUE - 1) = start + SECURE_RANDOM.nextInt(end - start + 1) 30 | 31 | /** 32 | * 获取安全的随机布尔值 33 | */ 34 | fun getBoolean() = SECURE_RANDOM.nextBoolean() 35 | 36 | /** 37 | * 获取安全的随机小数(Float),范围[start,end) 默认[0.0F,1.0F) 38 | */ 39 | fun getFloat(start: Float = 0F, end: Float = 1F) = start + SECURE_RANDOM.nextFloat() * (end - start) 40 | 41 | /** 42 | * 获取安全的随机长整型 43 | */ 44 | fun getLong() = SECURE_RANDOM.nextLong() 45 | 46 | /** 47 | * 给定判断给定概率是否生效 48 | * @return 满足概率返回false,否之返回true 49 | */ 50 | fun checkPercentage(percent: Int) = getDouble(end = 100.0) >= percent 51 | 52 | /** 53 | * 给定判断给定概率是否生效 54 | * @return 满足概率返回false,否之返回true 55 | */ 56 | fun checkPercentage(percent: Double) = getDouble(end = 100.0) >= percent 57 | 58 | /** 59 | * 获取以0为对称轴,范围为(-1,1)的高斯分布 60 | */ 61 | fun getGaussian() = SECURE_RANDOM.nextGaussian() 62 | 63 | /** 64 | * 范围区间内[start,end]的正态分布,中心轴为区间中心 65 | */ 66 | fun getGaussian(start: Double, end: Double): Double { 67 | val gaussian = getGaussian() 68 | val odd = if (abs(gaussian) < 3) gaussian / 3.00 else 1.00 69 | val half = (start + end) / 2.00 70 | return odd * (end - half) + half 71 | } 72 | 73 | /** 74 | * 加权随机区间 75 | * @return 选中的权重序号 76 | */ 77 | fun getWeighted(weights: Iterable): Int { 78 | val sum = weights.sum() 79 | val random = getDouble(0.0, sum) 80 | var temp = 0.0 81 | weights.forEachIndexed { index, i -> 82 | temp += i 83 | if (random <= temp) return index 84 | } 85 | return 0 86 | } 87 | 88 | //根据等级计算时运随机后的倍率 89 | fun calculateFortune(level: Int): Int { 90 | if (level <= 0) throw IllegalArgumentException("level can not <= 0") 91 | var temp = 2.0 / (level + 2) 92 | val otherRate = (1 - temp) / level 93 | val random = SECURE_RANDOM.nextDouble() 94 | for (n in 1..level + 1) { 95 | if (random <= temp) return n 96 | temp += otherRate 97 | } 98 | return 1 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /core/src/main/kotlin/top/iseason/bukkittemplate/utils/other/Submit.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkittemplate.utils.other 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.scheduler.BukkitTask 5 | import top.iseason.bukkittemplate.BukkitTemplate 6 | import java.util.concurrent.CompletableFuture 7 | 8 | /** 9 | * 提交一个 bukkit runnable任务 10 | * @param delay 延迟 单位tick 11 | * @param period 循环周期 单位tick 12 | * @param async 是否异步 13 | * @param task 你的任务 14 | */ 15 | fun submit( 16 | delay: Long = 0, 17 | period: Long = 0, 18 | async: Boolean = false, 19 | task: Runnable 20 | ): BukkitTask { 21 | check(delay >= 0) { "delay must grater than 0" } 22 | check(period >= 0) { "period must grater than 0" } 23 | return if (async) { 24 | if (period > 0) { 25 | Bukkit.getScheduler().runTaskTimerAsynchronously(BukkitTemplate.getPlugin(), task, delay, period) 26 | } else { 27 | Bukkit.getScheduler().runTaskLaterAsynchronously(BukkitTemplate.getPlugin(), task, delay) 28 | } 29 | } else { 30 | if (period > 0) { 31 | Bukkit.getScheduler().runTaskTimer(BukkitTemplate.getPlugin(), task, delay, period) 32 | } else { 33 | Bukkit.getScheduler().runTaskLater(BukkitTemplate.getPlugin(), task, delay) 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * 在主线程运行任务(使用BukkitRunnable) 40 | */ 41 | fun runSync(task: Runnable) = Bukkit.getScheduler().runTask(BukkitTemplate.getPlugin(), task) 42 | 43 | /** 44 | * 在异步线程运行任务 45 | */ 46 | fun runAsync(task: Runnable) { 47 | CompletableFuture.runAsync(task) 48 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 插件信息 2 | ## 插件名 3 | pluginName=SakuraBind 4 | ## 包名 5 | group=top.iseason.bukkit.sakurabind 6 | ## 作者 7 | author=Iseason 8 | ## jar包输出路径,windows下特殊字符前需要加\转义,默认输出到项目根目录的build文件夹下 9 | jarOutputFile=${root}/build 10 | ## 插件版本 11 | version=2.4.4 12 | # api设置 13 | kotlinVersion=2.1.10 14 | shadowJarVersion=8.3.6 15 | # 编译设置 16 | ## 是否混淆 17 | obfuscated=false 18 | ## 混淆的词典,留空默认使用 a-Z 19 | obfuscatedDictionary=proguard-dictionary\\iIl-dictionary.txt 20 | ## 是否删除未使用代码 21 | shrink=true 22 | ## exposed 数据库框架版本 23 | exposedVersion=0.59.0 24 | kotlin.code.style=official 25 | kotlin.incremental=true 26 | kotlin.incremental.java=true 27 | kotlin.caching.enabled=true 28 | kotlin.parallel.tasks.in.project=true 29 | org.gradle.caching=true 30 | org.gradle.parallel=true 31 | org.gradle.jvmargs=-Xmx1024m -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SakuraTown/SakuraBind/5216f0bd68191d8fa366fd520afd4c473c7c48c2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.12-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 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% equ 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 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /plugin/libs/BanItem_with_NBT_v3.2.37.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SakuraTown/SakuraBind/5216f0bd68191d8fa366fd520afd4c473c7c48c2/plugin/libs/BanItem_with_NBT_v3.2.37.jar -------------------------------------------------------------------------------- /plugin/libs/GermPlugin-API.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SakuraTown/SakuraBind/5216f0bd68191d8fa366fd520afd4c473c7c48c2/plugin/libs/GermPlugin-API.jar -------------------------------------------------------------------------------- /plugin/libs/GlobalMarketPlus-API.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SakuraTown/SakuraBind/5216f0bd68191d8fa366fd520afd4c473c7c48c2/plugin/libs/GlobalMarketPlus-API.jar -------------------------------------------------------------------------------- /plugin/libs/InvSync-2.3.42-API.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SakuraTown/SakuraBind/5216f0bd68191d8fa366fd520afd4c473c7c48c2/plugin/libs/InvSync-2.3.42-API.jar -------------------------------------------------------------------------------- /plugin/libs/PlayerDataSQL_v1.2.22.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SakuraTown/SakuraBind/5216f0bd68191d8fa366fd520afd4c473c7c48c2/plugin/libs/PlayerDataSQL_v1.2.22.jar -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/mgunlogson/cuckoofilter4j/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Copied from Apache Harmony and Lucene (6.2.0) projects with modifications 18 | */ 19 | package com.github.mgunlogson.cuckoofilter4j; 20 | 21 | /** 22 | * Some useful constants. 23 | **/ 24 | 25 | final class Constants { 26 | static final String OS_ARCH = System.getProperty("os.arch"); 27 | /** 28 | * True iff running on a 64bit JVM 29 | */ 30 | static final boolean JRE_IS_64BIT; 31 | 32 | static { 33 | boolean is64Bit; 34 | final String x = System.getProperty("sun.arch.data.model"); 35 | if (x != null) { 36 | is64Bit = x.contains("64"); 37 | } else { 38 | is64Bit = OS_ARCH != null && OS_ARCH.contains("64"); 39 | } 40 | JRE_IS_64BIT = is64Bit; 41 | } 42 | 43 | private Constants() { 44 | } // can't construct 45 | 46 | } -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/cuckoofilter/CuckooStrategies.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Brian Dupras 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package top.iseason.bukkit.sakurabind.cuckoofilter; 16 | 17 | import com.google.common.hash.Hashing; 18 | 19 | /** 20 | * Collections of strategies of generating the f-bit fingerprint, index i1 and index i2 required for 21 | * an element to be mapped to a CuckooTable of m buckets with hash function h. These strategies are 22 | * part of the serialized form of the Cuckoo filters that use them, thus they must be preserved as 23 | * is (no updates allowed, only introduction of new versions).

Important: the order of the 24 | * constants cannot change, and they cannot be deleted - we depend on their ordinal for CuckooFilter 25 | * serialization. 26 | * 27 | * @author Brian Dupras 28 | */ 29 | public enum CuckooStrategies { 30 | /** 31 | * Adaptation of "Cuckoo Filter: Practically Better Than Bloom", Bin Fan, et al, that is 32 | * comparable to a Bloom Filter's memory efficiency, supports entry deletion, and can accept up to 33 | * 12.8 billion entries at 3% FPP. 34 | * 35 | *

This strategy uses 32 bits of {@link Hashing#murmur3_128} to find an entry's primary index. 36 | * The next non-zero f-bit segment of the hash is used as the entry's fingerprint. An entry's 37 | * alternate index is defined as {@code [hash(fingerprint) * parsign(index)] modulo bucket_count}, 38 | * where {@code hash(fingerprint)} is always odd, and {@code parsign(index)} is defined as {@code 39 | * +1} when {@code index} is even and {@code -1} when {@code index} is odd. The filter's bucket 40 | * count is rounded up to an even number. By specifying an even number of buckets and an odd 41 | * fingerprint hash, the parity of the alternate index is guaranteed to be opposite the parity of 42 | * the primary index. The use of the index's parity to apply a sign to {@code hash(fingerprint)} 43 | * causes the operation to be reversible, i.e. {@code index(e) == altIndex(altIndex(e))}.

44 | * 45 | *

A notable difference of this strategy from "Cuckoo Filter" is the method of selecting an 46 | * entry's alternate index. In the paper, the alternate index is defined as {@code index xor 47 | * hash(fingerprint)}. The use of {@code xor} requires that the index space be defined as 48 | * [0..2^f]. The side-effect of this is that the Cuckoo Filter's bucket count must be a power of 49 | * 2, meaning the memory utilization of the filter must be "rounded up" to the next power of two. 50 | * This side-effect of the paper's algorithm is avoided by the algorithm as described above.

51 | */ 52 | MURMUR128_BEALDUPRAS_32() { 53 | @Override 54 | public CuckooStrategy strategy() { 55 | return new CuckooStrategyMurmurBealDupras32(this.ordinal()); 56 | } 57 | }; 58 | 59 | public abstract CuckooStrategy strategy(); 60 | } 61 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/cuckoofilter/CuckooStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Brian Dupras 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package top.iseason.bukkit.sakurabind.cuckoofilter; 16 | 17 | import com.google.common.hash.Funnel; 18 | 19 | import java.io.Serializable; 20 | 21 | interface CuckooStrategy extends Serializable { 22 | int ordinal(); 23 | 24 | boolean add(T object, Funnel funnel, CuckooTable table); 25 | 26 | boolean remove(T object, Funnel funnel, CuckooTable table); 27 | 28 | boolean contains(T object, Funnel funnel, CuckooTable table); 29 | 30 | boolean addAll(CuckooTable thiz, CuckooTable that); 31 | 32 | boolean equivalent(CuckooTable thiz, CuckooTable that); 33 | 34 | boolean containsAll(CuckooTable thiz, CuckooTable that); 35 | 36 | boolean removeAll(CuckooTable thiz, CuckooTable that); 37 | } 38 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/AutoBindMessageEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.HumanEntity; 5 | import org.bukkit.event.Cancellable; 6 | import org.bukkit.event.Event; 7 | import org.bukkit.event.HandlerList; 8 | import org.bukkit.inventory.ItemStack; 9 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 10 | 11 | public class AutoBindMessageEvent extends Event implements Cancellable { 12 | private static final HandlerList handlers = new HandlerList(); 13 | private final ItemStack item; 14 | private final HumanEntity player; 15 | private final BaseSetting setting; 16 | private boolean isCancelled = false; 17 | /** 18 | * 提示的消息 19 | */ 20 | private String message; 21 | /** 22 | * 是否正在冷却中,是由config.yml中的 message-coolDown 控制 23 | */ 24 | private Boolean isCoolDown; 25 | 26 | 27 | public AutoBindMessageEvent(HumanEntity player, BaseSetting setting, String message, Boolean isCoolDown, ItemStack item) { 28 | super(!Bukkit.isPrimaryThread()); 29 | 30 | this.player = player; 31 | this.setting = setting; 32 | this.message = message; 33 | this.isCoolDown = isCoolDown; 34 | this.item = item; 35 | } 36 | 37 | public static HandlerList getHandlerList() { 38 | return handlers; 39 | } 40 | 41 | @Override 42 | public HandlerList getHandlers() { 43 | return handlers; 44 | } 45 | 46 | @Override 47 | public boolean isCancelled() { 48 | return isCancelled; 49 | } 50 | 51 | @Override 52 | public void setCancelled(boolean cancel) { 53 | isCancelled = cancel; 54 | } 55 | 56 | 57 | public String getMessage() { 58 | return message; 59 | } 60 | 61 | public void setMessage(String message) { 62 | this.message = message; 63 | } 64 | 65 | public Boolean getCoolDown() { 66 | return isCoolDown; 67 | } 68 | 69 | public void setCoolDown(Boolean coolDown) { 70 | isCoolDown = coolDown; 71 | } 72 | 73 | public ItemStack getItem() { 74 | return item; 75 | } 76 | 77 | public BaseSetting getSetting() { 78 | return setting; 79 | } 80 | 81 | public HumanEntity getPlayer() { 82 | return player; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/BindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 7 | import top.iseason.bukkit.sakurabind.utils.BindType; 8 | 9 | import java.util.UUID; 10 | 11 | public abstract class BindEvent extends Event implements Cancellable { 12 | 13 | private boolean isCancelled = false; 14 | private BaseSetting setting; 15 | private BindType bindType; 16 | private UUID owner; 17 | 18 | public BindEvent(BaseSetting setting, UUID owner, BindType bindType) { 19 | super(!Bukkit.isPrimaryThread()); 20 | this.setting = setting; 21 | this.owner = owner; 22 | this.bindType = bindType; 23 | } 24 | 25 | @Override 26 | public boolean isCancelled() { 27 | return isCancelled; 28 | } 29 | 30 | @Override 31 | public void setCancelled(boolean cancelled) { 32 | isCancelled = cancelled; 33 | } 34 | 35 | public BaseSetting getSetting() { 36 | return setting; 37 | } 38 | 39 | public void setSetting(BaseSetting setting) { 40 | this.setting = setting; 41 | } 42 | 43 | public BindType getBindType() { 44 | return bindType; 45 | } 46 | 47 | public void setBindType(BindType bindType) { 48 | this.bindType = bindType; 49 | } 50 | 51 | public UUID getOwner() { 52 | return owner; 53 | } 54 | 55 | public void setOwner(UUID owner) { 56 | this.owner = owner; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/BlockBindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.event.HandlerList; 5 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | public class BlockBindEvent extends BindEvent { 13 | private static final HandlerList handlers = new HandlerList(); 14 | /** 15 | * 绑定的物品 16 | */ 17 | private final Block block; 18 | 19 | private List extraData = null; 20 | 21 | public BlockBindEvent(Block block, BaseSetting setting, UUID uuid, BindType bindType) { 22 | super(setting, uuid, bindType); 23 | this.block = block; 24 | } 25 | 26 | public static HandlerList getHandlerList() { 27 | return handlers; 28 | } 29 | 30 | @Override 31 | public HandlerList getHandlers() { 32 | return handlers; 33 | } 34 | 35 | /** 36 | * 此处的数据会随着方块主人、配置一起储存。 37 | * 最终会使用类似 String::join 的方式组合成一个字符串 38 | * 分隔符 delimiter 是制表符 '\t' 请不要在数据中出现 '\t' 39 | */ 40 | public List getExtraData() { 41 | if (extraData == null) { 42 | extraData = new ArrayList<>(); 43 | } 44 | return extraData; 45 | } 46 | 47 | public void setExtraData(List extraData) { 48 | this.extraData = extraData; 49 | } 50 | 51 | public Block getBlock() { 52 | return block; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/BlockBindFromItemEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.HandlerList; 6 | import org.bukkit.inventory.ItemStack; 7 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 8 | import top.iseason.bukkit.sakurabind.utils.BindType; 9 | 10 | import java.util.UUID; 11 | 12 | public class BlockBindFromItemEvent extends BlockBindEvent { 13 | private static final HandlerList handlers = new HandlerList(); 14 | 15 | /** 16 | * 玩家手上的物品 17 | */ 18 | private final ItemStack handItem; 19 | 20 | private final Player player; 21 | 22 | private final boolean isMultiPlace; 23 | 24 | public BlockBindFromItemEvent(Block block, BaseSetting setting, UUID uuid, BindType bindType, ItemStack handItem, Player player, boolean isMultiPlace) { 25 | super(block, setting, uuid, bindType); 26 | this.handItem = handItem; 27 | this.player = player; 28 | this.isMultiPlace = isMultiPlace; 29 | } 30 | 31 | public static HandlerList getHandlerList() { 32 | return handlers; 33 | } 34 | 35 | @Override 36 | public HandlerList getHandlers() { 37 | return handlers; 38 | } 39 | 40 | /** 41 | * 玩家手上的物品 42 | */ 43 | public ItemStack getHandItem() { 44 | return handItem; 45 | } 46 | 47 | public Player getPlayer() { 48 | return player; 49 | } 50 | 51 | public boolean isMultiPlace() { 52 | return isMultiPlace; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/BlockUnBindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.event.HandlerList; 5 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | import java.util.UUID; 9 | 10 | public class BlockUnBindEvent extends UnBindEvent { 11 | private static final HandlerList handlers = new HandlerList(); 12 | private final Block block; 13 | 14 | public BlockUnBindEvent(Block block, BaseSetting setting, UUID owner, BindType bindType) { 15 | super(setting, owner, bindType); 16 | this.block = block; 17 | } 18 | 19 | public static HandlerList getHandlerList() { 20 | return handlers; 21 | } 22 | 23 | public Block getBlock() { 24 | return block; 25 | } 26 | 27 | @Override 28 | public HandlerList getHandlers() { 29 | return handlers; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/EntityBindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.entity.Entity; 4 | import org.bukkit.event.HandlerList; 5 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | import java.util.UUID; 9 | 10 | public class EntityBindEvent extends BindEvent { 11 | private static final HandlerList handlers = new HandlerList(); 12 | /** 13 | * 绑定的物品 14 | */ 15 | private final Entity entity; 16 | 17 | public EntityBindEvent(Entity entity, BaseSetting setting, UUID uuid, BindType bindType) { 18 | super(setting, uuid, bindType); 19 | this.entity = entity; 20 | } 21 | 22 | public static HandlerList getHandlerList() { 23 | return handlers; 24 | } 25 | 26 | @Override 27 | public HandlerList getHandlers() { 28 | return handlers; 29 | } 30 | 31 | public Entity getEntity() { 32 | return entity; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/EntityUnBindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.entity.Entity; 4 | import org.bukkit.event.HandlerList; 5 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | import java.util.UUID; 9 | 10 | public class EntityUnBindEvent extends UnBindEvent { 11 | private static final HandlerList handlers = new HandlerList(); 12 | 13 | 14 | private final Entity entity; 15 | 16 | public EntityUnBindEvent(Entity entity, BaseSetting setting, UUID owner, BindType bindType) { 17 | super(setting, owner, bindType); 18 | this.entity = entity; 19 | } 20 | 21 | public static HandlerList getHandlerList() { 22 | return handlers; 23 | } 24 | 25 | public Entity getEntity() { 26 | return entity; 27 | } 28 | 29 | @Override 30 | public HandlerList getHandlers() { 31 | return handlers; 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/ItemBindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.event.HandlerList; 4 | import org.bukkit.inventory.ItemStack; 5 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | import java.util.UUID; 9 | 10 | public class ItemBindEvent extends BindEvent { 11 | private static final HandlerList handlers = new HandlerList(); 12 | /** 13 | * 绑定的物品 14 | */ 15 | private final ItemStack item; 16 | 17 | 18 | public ItemBindEvent(ItemStack item, BaseSetting setting, UUID uuid, BindType bindType) { 19 | super(setting, uuid, bindType); 20 | this.item = item; 21 | } 22 | 23 | public static HandlerList getHandlerList() { 24 | return handlers; 25 | } 26 | 27 | @Override 28 | public HandlerList getHandlers() { 29 | return handlers; 30 | } 31 | 32 | public ItemStack getItem() { 33 | return item; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/ItemBindFromBlockEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.event.HandlerList; 4 | import org.bukkit.inventory.ItemStack; 5 | import top.iseason.bukkit.sakurabind.cache.BlockInfo; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | public class ItemBindFromBlockEvent extends ItemBindEvent { 9 | private static final HandlerList handlers = new HandlerList(); 10 | 11 | private BlockInfo blockInfo; 12 | 13 | public ItemBindFromBlockEvent(ItemStack item, BlockInfo blockInfo, BindType bindType) { 14 | super(item, blockInfo.getSetting(), blockInfo.getOwnerUUID(), bindType); 15 | this.blockInfo = blockInfo; 16 | } 17 | 18 | public static HandlerList getHandlerList() { 19 | return handlers; 20 | } 21 | 22 | @Override 23 | public HandlerList getHandlers() { 24 | return handlers; 25 | } 26 | 27 | public BlockInfo getBlockInfo() { 28 | return blockInfo; 29 | } 30 | 31 | public void setBlockInfo(BlockInfo blockInfo) { 32 | if (blockInfo == null) throw new NullPointerException("blockInfo must not be null!"); 33 | this.blockInfo = blockInfo; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/ItemMatchedEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import org.bukkit.inventory.ItemStack; 8 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 9 | 10 | public class ItemMatchedEvent extends Event implements Cancellable { 11 | private static final HandlerList handlers = new HandlerList(); 12 | /** 13 | * 被匹配的物品 14 | */ 15 | private final ItemStack item; 16 | private boolean isCancelled = false; 17 | /** 18 | * 匹配的设置,设置为null将会使用全局设置 19 | */ 20 | private BaseSetting matchSetting; 21 | 22 | public ItemMatchedEvent(ItemStack item, BaseSetting matchSetting) { 23 | super(!Bukkit.isPrimaryThread()); 24 | this.item = item; 25 | this.matchSetting = matchSetting; 26 | } 27 | 28 | public static HandlerList getHandlerList() { 29 | return handlers; 30 | } 31 | 32 | @Override 33 | public HandlerList getHandlers() { 34 | return handlers; 35 | } 36 | 37 | @Override 38 | public boolean isCancelled() { 39 | return isCancelled; 40 | } 41 | 42 | @Override 43 | public void setCancelled(boolean cancel) { 44 | isCancelled = cancel; 45 | } 46 | 47 | public BaseSetting getMatchSetting() { 48 | return matchSetting; 49 | } 50 | 51 | public void setMatchSetting(BaseSetting matchSetting) { 52 | this.matchSetting = matchSetting; 53 | } 54 | 55 | public ItemStack getItem() { 56 | return item; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/ItemSendBackEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import org.bukkit.inventory.ItemStack; 8 | import top.iseason.bukkit.sakurabind.utils.SendBackType; 9 | 10 | import java.util.UUID; 11 | 12 | /* 13 | * 物品批量送回事件,可取消 14 | * */ 15 | public class ItemSendBackEvent extends Event implements Cancellable { 16 | private static final HandlerList handlers = new HandlerList(); 17 | private final ItemStack[] items; 18 | private final UUID owner; 19 | private final SendBackType type; 20 | private boolean isCancelled = false; 21 | 22 | public ItemSendBackEvent(UUID owner, SendBackType type, ItemStack[] item) { 23 | super(!Bukkit.isPrimaryThread()); 24 | this.owner = owner; 25 | this.type = type; 26 | this.items = item; 27 | } 28 | 29 | public static HandlerList getHandlerList() { 30 | return handlers; 31 | } 32 | 33 | @Override 34 | public HandlerList getHandlers() { 35 | return handlers; 36 | } 37 | 38 | @Override 39 | public boolean isCancelled() { 40 | return isCancelled; 41 | } 42 | 43 | @Override 44 | public void setCancelled(boolean cancel) { 45 | isCancelled = cancel; 46 | } 47 | 48 | public ItemStack[] getItems() { 49 | return items; 50 | } 51 | 52 | public UUID getOwner() { 53 | return owner; 54 | } 55 | 56 | public SendBackType getType() { 57 | return type; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/ItemUnBIndEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.event.HandlerList; 4 | import org.bukkit.inventory.ItemStack; 5 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 6 | import top.iseason.bukkit.sakurabind.utils.BindType; 7 | 8 | import java.util.UUID; 9 | 10 | public class ItemUnBIndEvent extends UnBindEvent { 11 | private static final HandlerList handlers = new HandlerList(); 12 | private final ItemStack item; 13 | 14 | public ItemUnBIndEvent(ItemStack item, UUID uuid, BaseSetting setting, BindType bindType) { 15 | super(setting, uuid, bindType); 16 | this.item = item; 17 | } 18 | 19 | public static HandlerList getHandlerList() { 20 | return handlers; 21 | } 22 | 23 | @Override 24 | public HandlerList getHandlers() { 25 | return handlers; 26 | } 27 | 28 | public ItemStack getItem() { 29 | return item; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/java/top/iseason/bukkit/sakurabind/event/UnBindEvent.java: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.event; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import top.iseason.bukkit.sakurabind.config.BaseSetting; 7 | import top.iseason.bukkit.sakurabind.utils.BindType; 8 | 9 | import java.util.UUID; 10 | 11 | public abstract class UnBindEvent extends Event implements Cancellable { 12 | 13 | private final UUID owner; 14 | private boolean isCancelled = false; 15 | private BaseSetting setting; 16 | private BindType bindType; 17 | 18 | public UnBindEvent(BaseSetting setting, UUID owner, BindType bindType) { 19 | super(!Bukkit.isPrimaryThread()); 20 | this.setting = setting; 21 | this.owner = owner; 22 | this.bindType = bindType; 23 | } 24 | 25 | @Override 26 | public boolean isCancelled() { 27 | return isCancelled; 28 | } 29 | 30 | @Override 31 | public void setCancelled(boolean cancelled) { 32 | isCancelled = cancelled; 33 | } 34 | 35 | public BaseSetting getSetting() { 36 | return setting; 37 | } 38 | 39 | public void setSetting(BaseSetting setting) { 40 | this.setting = setting; 41 | } 42 | 43 | public BindType getBindType() { 44 | return bindType; 45 | } 46 | 47 | public void setBindType(BindType bindType) { 48 | this.bindType = bindType; 49 | } 50 | 51 | public UUID getOwner() { 52 | return owner; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/cache/BaseCache.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.cache 2 | 3 | import com.github.mgunlogson.cuckoofilter4j.CuckooFilter 4 | import com.google.common.hash.Hashing 5 | import org.ehcache.PersistentCacheManager 6 | import org.ehcache.config.builders.CacheManagerBuilder 7 | import top.iseason.bukkittemplate.debug.warn 8 | import java.io.File 9 | import java.io.ObjectInputStream 10 | import java.io.ObjectOutputStream 11 | 12 | abstract class BaseCache { 13 | 14 | private var rebuildFilter = false 15 | 16 | /** 17 | * 设置缓存 18 | */ 19 | abstract fun setCache(builder: CacheManagerBuilder): CacheManagerBuilder 20 | 21 | /** 22 | * 初始化 23 | */ 24 | open fun init(cacheManager: org.ehcache.CacheManager) { 25 | if (rebuildFilter) { 26 | rebuildFilter = false 27 | reloadFilter() 28 | } 29 | } 30 | 31 | open fun reloadFilter() {} 32 | 33 | /** 34 | * 保存 35 | */ 36 | abstract fun onSave() 37 | 38 | inline fun string2FilterKey(str: String): Long = Hashing.murmur3_128().hashUnencodedChars(str).asLong() 39 | 40 | fun loadFilter(file: File): CuckooFilter { 41 | return if (!file.exists()) 42 | newFilter() 43 | else try { 44 | ObjectInputStream(file.inputStream()).use { it.readObject() as CuckooFilter } 45 | } catch (e: Exception) { 46 | rebuildFilter = true 47 | warn("error while loading filter $file , create new filter") 48 | e.printStackTrace() 49 | newFilter() 50 | } 51 | } 52 | 53 | 54 | fun saveFilter(file: File, filter: CuckooFilter) { 55 | if (!file.exists()) 56 | file.createNewFile() 57 | ObjectOutputStream(file.outputStream()).use { 58 | it.writeObject(filter) 59 | } 60 | 61 | } 62 | 63 | open fun newFilter() = CuckooFilter 64 | .Builder(65536) 65 | .withFalsePositiveRate(0.0001) 66 | .withExpectedConcurrency(2) 67 | .build() 68 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/cache/BlockInfo.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.cache 2 | 3 | import top.iseason.bukkit.sakurabind.config.BaseSetting 4 | import top.iseason.bukkit.sakurabind.config.ItemSettings 5 | import java.util.* 6 | 7 | data class BlockInfo( 8 | /** 9 | * 物主 10 | */ 11 | val owner: String, 12 | /** 13 | * 绑定设置 14 | */ 15 | val setting: BaseSetting, 16 | /** 17 | * 额外数据 18 | */ 19 | val extraData: List = emptyList() 20 | ) { 21 | val ownerUUID: UUID by lazy { UUID.fromString(owner) } 22 | 23 | fun serialize(): String { 24 | val value = if (setting.keyPath != "global-setting") 25 | "$owner,$setting" 26 | else owner 27 | if (extraData.isEmpty()) { 28 | return value 29 | } else { 30 | return extraData.joinToString(prefix = "${value}\t", separator = "\t") 31 | } 32 | } 33 | 34 | companion object { 35 | 36 | @JvmStatic 37 | fun deserialize(str: String): BlockInfo { 38 | val strings = str.split('\t') 39 | val split = strings.first().split(',') 40 | return if (strings.size > 1) { 41 | BlockInfo(split[0], ItemSettings.getSetting(split.getOrNull(1)), strings.drop(1)) 42 | } else { 43 | BlockInfo(split[0], ItemSettings.getSetting(split.getOrNull(1))) 44 | } 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/cache/EntityCache.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.cache 2 | 3 | import com.github.mgunlogson.cuckoofilter4j.CuckooFilter 4 | import org.bukkit.entity.Entity 5 | import org.ehcache.Cache 6 | import org.ehcache.CacheManager 7 | import org.ehcache.PersistentCacheManager 8 | import org.ehcache.config.builders.CacheConfigurationBuilder 9 | import org.ehcache.config.builders.CacheManagerBuilder 10 | import org.ehcache.config.builders.ExpiryPolicyBuilder 11 | import org.ehcache.config.builders.ResourcePoolsBuilder 12 | import org.ehcache.config.units.EntryUnit 13 | import org.ehcache.config.units.MemoryUnit 14 | import top.iseason.bukkit.sakurabind.config.BaseSetting 15 | import top.iseason.bukkit.sakurabind.config.ItemSettings 16 | import top.iseason.bukkittemplate.BukkitTemplate 17 | import java.io.File 18 | 19 | 20 | object EntityCache : BaseCache() { 21 | private val filterFile = File(BukkitTemplate.getPlugin().dataFolder, "data${File.separator}Filter-Entity") 22 | private lateinit var entityCache: Cache 23 | 24 | private val entityFilter = loadFilter(filterFile) 25 | 26 | override fun newFilter() = CuckooFilter 27 | .Builder(32768) 28 | .withFalsePositiveRate(0.0001) 29 | .withExpectedConcurrency(2) 30 | .build() 31 | 32 | override fun setCache(builder: CacheManagerBuilder): CacheManagerBuilder { 33 | return builder.withCache( 34 | "Entity-owner", 35 | CacheConfigurationBuilder.newCacheConfigurationBuilder( 36 | String::class.java, String::class.java, 37 | ResourcePoolsBuilder.newResourcePoolsBuilder() 38 | .heap(100, EntryUnit.ENTRIES) 39 | .offheap(10, MemoryUnit.MB) 40 | .disk(200, MemoryUnit.MB, true) 41 | ).withExpiry(ExpiryPolicyBuilder.noExpiration()) 42 | // .withService(OffHeapDiskStoreConfiguration(2)) 43 | .build() 44 | ) 45 | } 46 | 47 | override fun init(cacheManager: CacheManager) { 48 | entityCache = cacheManager.getCache("Entity-owner", String::class.java, String::class.java)!! 49 | super.init(cacheManager) 50 | } 51 | 52 | override fun reloadFilter() { 53 | val iterator = entityCache.iterator() 54 | while (iterator.hasNext()) { 55 | val next = iterator.next() 56 | entityFilter.put(string2FilterKey(next.key)) 57 | } 58 | } 59 | 60 | override fun onSave() { 61 | saveFilter(filterFile, entityFilter) 62 | } 63 | 64 | fun getEntityInfo(entity: Entity): Pair? { 65 | //使用布谷鸟过滤防止缓存穿透 66 | val uuid = entity.uniqueId.toString() 67 | if (!entityFilter.mightContain(string2FilterKey(uuid))) return null 68 | val get = entityCache.get(uuid) ?: return null 69 | val split = get.split(',') 70 | return split[0] to ItemSettings.getSetting(split.getOrNull(1)) 71 | } 72 | 73 | fun addEntity(entity: Entity, owner: String, setting: String?) { 74 | val value = if (setting != null && setting != "global-setting") 75 | "$owner,$setting" 76 | else owner 77 | addEntity(entity, value) 78 | } 79 | 80 | fun addEntity(entity: Entity, value: String) { 81 | val uuid = entity.uniqueId.toString() 82 | if (!entityCache.containsKey(value)) { 83 | entityFilter.put(string2FilterKey(uuid)) 84 | } 85 | entityCache.put(uuid, value) 86 | } 87 | 88 | fun removeEntity(entity: Entity) { 89 | val uuid = entity.uniqueId.toString() 90 | if (entityCache.containsKey(uuid)) { 91 | entityCache.remove(uuid) 92 | entityFilter.delete(string2FilterKey(uuid)) 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/AutoBindCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import de.tr7zw.nbtapi.NBT 4 | 5 | import org.bukkit.entity.Player 6 | import org.bukkit.permissions.PermissionDefault 7 | import top.iseason.bukkit.sakurabind.config.Config 8 | import top.iseason.bukkit.sakurabind.config.Lang 9 | import top.iseason.bukkittemplate.command.CommandNode 10 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 11 | import top.iseason.bukkittemplate.command.Param 12 | import top.iseason.bukkittemplate.command.ParmaException 13 | import top.iseason.bukkittemplate.utils.bukkit.EntityUtils.getHeldItem 14 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 15 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 16 | 17 | object AutoBindCommand : CommandNode( 18 | name = "autoBind", 19 | description = "给手上的物品添加自动绑定的NBT", 20 | default = PermissionDefault.OP, 21 | isPlayerOnly = true, 22 | async = true, 23 | params = listOf( 24 | Param("[nbtKey]", suggestRuntime = { Config.auto_bind_nbt.split('.') }), 25 | Param("[nbtvalue]"), 26 | ) 27 | ) { 28 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 29 | val player = sender as Player 30 | val nbt = params.nextOrNull() 31 | val value = params.nextOrNull() ?: "" 32 | val heldItem = player.getHeldItem() ?: throw ParmaException("请拿着物品") 33 | if (nbt == null) { 34 | NBT.modify(heldItem) { 35 | it.setString(Config.auto_bind_nbt, value) 36 | } 37 | if (!params.hasParma("-silent")) 38 | player.sendColorMessage(Lang.command__autoBind.formatBy(Config.auto_bind_nbt)) 39 | } else { 40 | NBT.modify(heldItem) { 41 | it.setString(nbt, value) 42 | } 43 | player.sendColorMessage(Lang.command__autoBind.formatBy(nbt)) 44 | } 45 | 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/BindAllCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkit.sakurabind.utils.BindType 8 | import top.iseason.bukkit.sakurabind.utils.MessageTool 9 | import top.iseason.bukkittemplate.command.CommandNode 10 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 11 | import top.iseason.bukkittemplate.command.Param 12 | import top.iseason.bukkittemplate.command.ParamSuggestCache 13 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 14 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 15 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 16 | 17 | object BindAllCommand : CommandNode( 18 | name = "bindAll", 19 | description = "绑定某玩家背包里的所有物品", 20 | default = PermissionDefault.OP, 21 | params = listOf( 22 | Param("", suggestRuntime = ParamSuggestCache.playerParam), 23 | Param("[-noLore]", suggest = listOf("-noLore")) 24 | ), 25 | async = true 26 | ) { 27 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 28 | val player = params.getParam(0) 29 | val showLore = !params.hasParma("-noLore") 30 | val hasParma = params.hasParma("-silent") 31 | for (itemStack in player.inventory) { 32 | if (itemStack.checkAir()) continue 33 | SakuraBindAPI.bind(itemStack, player, showLore, BindType.COMMAND_BIND_ITEM) 34 | } 35 | player.updateInventory() 36 | if (!hasParma) { 37 | sender.sendColorMessage(Lang.command__bindAll.formatBy(player.name)) 38 | MessageTool.messageCoolDown(player, Lang.item_bind_all) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/CallbackCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.config.Lang 6 | import top.iseason.bukkittemplate.command.CommandNode 7 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 8 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 9 | import java.util.* 10 | 11 | // 召回自己的物品 12 | object CallbackCommand : CommandNode( 13 | name = "callback", 14 | description = "切换物品召回模式,将召回属于自己的物品", 15 | default = PermissionDefault.TRUE, 16 | isPlayerOnly = true, 17 | async = true 18 | ) { 19 | private val callbackSet = hashSetOf() 20 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 21 | val player = sender as Player 22 | val isSilent = params.hasParma("-silent") 23 | val uid = player.uniqueId.toString() 24 | if (callbackSet.contains(uid)) { 25 | callbackSet.remove(uid) 26 | if (!isSilent) 27 | sender.sendColorMessage(Lang.command__callback_off) 28 | } else { 29 | callbackSet.add(uid) 30 | if (!isSilent) 31 | sender.sendColorMessage(Lang.command__callback_on) 32 | } 33 | } 34 | 35 | /** 36 | * 该物主是否启用召回模式 37 | */ 38 | fun isCallback(uuid: UUID?) = if (uuid != null) callbackSet.contains(uuid.toString()) else false 39 | 40 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/DebugCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.config.Lang 6 | import top.iseason.bukkittemplate.command.CommandNode 7 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 8 | import top.iseason.bukkittemplate.command.Param 9 | import top.iseason.bukkittemplate.command.ParamSuggestCache 10 | import top.iseason.bukkittemplate.debug.SimpleLogger 11 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 12 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 13 | import java.util.* 14 | import java.util.concurrent.ConcurrentHashMap 15 | 16 | object DebugCommand : CommandNode( 17 | name = "debug", 18 | description = "切换debug模式", 19 | default = PermissionDefault.OP, 20 | async = true, 21 | params = listOf(Param("[player]", suggestRuntime = ParamSuggestCache.playerParam)) 22 | ) { 23 | val debugPlayer = ConcurrentHashMap.newKeySet() 24 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 25 | val player = params.nextOrNull() 26 | if (player != null) { 27 | val uid = player.uniqueId 28 | if (debugPlayer.remove(uid)) { 29 | if (!params.hasParma("-silent")) 30 | sender.sendColorMessage(Lang.command__debug_player_close.formatBy(player.name)) 31 | } else { 32 | debugPlayer.add(player.uniqueId) 33 | if (!params.hasParma("-silent")) 34 | sender.sendColorMessage(Lang.command__debug_player_open.formatBy(player.name)) 35 | } 36 | } else { 37 | SimpleLogger.isDebug = !SimpleLogger.isDebug 38 | if (!params.hasParma("-silent")) 39 | sender.sendColorMessage(Lang.command__debug.formatBy(SimpleLogger.isDebug)) 40 | } 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/NBTCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | 4 | import org.bukkit.entity.Player 5 | import org.bukkit.permissions.PermissionDefault 6 | import top.iseason.bukkittemplate.command.CommandNode 7 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 8 | import top.iseason.bukkittemplate.command.ParmaException 9 | import top.iseason.bukkittemplate.utils.bukkit.EntityUtils.getHeldItem 10 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.toJson 11 | 12 | object NBTCommand : CommandNode( 13 | name = "nbt", 14 | description = "在控制台输出手上物品的NBT", 15 | default = PermissionDefault.OP, 16 | async = true, 17 | isPlayerOnly = true 18 | ) { 19 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 20 | val player = sender as Player 21 | val heldItem = player.inventory.getHeldItem() ?: throw ParmaException("请拿着物品") 22 | println(heldItem.toJson()) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/ReloadCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.permissions.PermissionDefault 4 | import top.iseason.bukkit.sakurabind.SakuraBind 5 | import top.iseason.bukkittemplate.command.CommandNode 6 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 7 | import top.iseason.bukkittemplate.config.DatabaseConfig 8 | 9 | object ReloadCommand : CommandNode( 10 | name = "reload", 11 | description = "重载配置", 12 | default = PermissionDefault.OP, 13 | async = true 14 | ) { 15 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { _, _ -> 16 | DatabaseConfig.closeDB() 17 | SakuraBind.initConfig() 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/RootCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import top.iseason.bukkittemplate.command.CommandNode 4 | 5 | object RootCommand : CommandNode( 6 | name = "sakurabind", 7 | alias = arrayOf("sbind", "sb", "sab", "bind"), 8 | description = "樱花绑定根节点" 9 | ) 10 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/SelectCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.block.Block 4 | import org.bukkit.entity.Entity 5 | import org.bukkit.entity.Player 6 | import org.bukkit.inventory.ItemStack 7 | import org.bukkit.permissions.PermissionDefault 8 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 9 | import top.iseason.bukkit.sakurabind.config.Config 10 | import top.iseason.bukkit.sakurabind.config.DefaultItemSetting 11 | import top.iseason.bukkit.sakurabind.config.ItemSettings 12 | import top.iseason.bukkit.sakurabind.config.Lang 13 | import top.iseason.bukkit.sakurabind.listener.SelectListener 14 | import top.iseason.bukkit.sakurabind.utils.BindType 15 | import top.iseason.bukkittemplate.command.CommandNode 16 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 17 | import top.iseason.bukkittemplate.command.Param 18 | import top.iseason.bukkittemplate.command.ParamSuggestCache 19 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 20 | import top.iseason.bukkittemplate.utils.other.submit 21 | 22 | object SelectCommand : CommandNode( 23 | name = "select", 24 | description = "为玩家开启/关闭目标选择模式", 25 | default = PermissionDefault.OP, 26 | async = true, 27 | isPlayerOnly = true, 28 | params = listOf( 29 | Param("", suggestRuntime = ParamSuggestCache.playerParam), 30 | Param("[bind]", listOf("bind")) 31 | ) 32 | ) { 33 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, _ -> 34 | val player = params.next() 35 | val silent = params.hasParma("-silent") 36 | val next = params.nextOrNull() 37 | if ("bind" == next) { 38 | when (val any = SelectListener.selecting[player]) { 39 | is ItemStack -> { 40 | SakuraBindAPI.bind(any, player, type = BindType.COMMAND_BIND_ITEM) 41 | } 42 | 43 | is Block -> { 44 | SakuraBindAPI.bindBlock( 45 | any, 46 | player.uniqueId, 47 | ItemSettings.getSetting(any.drops.first()), 48 | BindType.COMMAND_BIND_BLOCK 49 | ) 50 | } 51 | 52 | is Entity -> { 53 | SakuraBindAPI.bindEntity(any, player, DefaultItemSetting, BindType.COMMAND_BIND_ENTITY) 54 | } 55 | 56 | else -> { 57 | player.sendColorMessage(Lang.command__select_no_selected) 58 | return@CommandNodeExecutor 59 | } 60 | } 61 | SelectListener.selecting.remove(player) 62 | if (!silent) { 63 | player.sendColorMessage(Lang.command__select_bind) 64 | } 65 | return@CommandNodeExecutor 66 | } 67 | if (SelectListener.selecting.containsKey(player)) { 68 | if (!silent) 69 | player.sendColorMessage(Lang.command__select_off) 70 | SelectListener.selecting.remove(player) 71 | } else { 72 | if (!silent) 73 | player.sendColorMessage(Lang.command__select_on) 74 | SelectListener.selecting[player] = true 75 | submit(Config.command_select_timeout, async = true) { 76 | if (!SelectListener.selecting.containsKey(player)) return@submit 77 | SelectListener.selecting.remove(player) 78 | player.sendColorMessage(Lang.command__select_timeout) 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/TestCacheCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import com.google.common.cache.CacheStats 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.config.Config 6 | import top.iseason.bukkit.sakurabind.config.ItemSettings 7 | import top.iseason.bukkittemplate.command.CommandNode 8 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 9 | 10 | object TestCacheCommand : CommandNode( 11 | name = "cache", 12 | description = "输出缓存信息", 13 | default = PermissionDefault.OP, 14 | async = true 15 | ) { 16 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 17 | val itemCache = ItemSettings.getCacheStats().toStr() 18 | sender.sendMessage("物品缓存统计: $itemCache") 19 | if (Config.data_migration__enable) { 20 | val migCache = Config.getDataMigrationCacheStat()!!.toStr() 21 | sender.sendMessage("迁移缓存统计: $migCache") 22 | } 23 | } 24 | 25 | private fun CacheStats.toStr(): String { 26 | val stringBuilder = StringBuilder() 27 | stringBuilder.append("请求数: ") 28 | stringBuilder.append(requestCount()) 29 | stringBuilder.append(" | 命中率: ") 30 | stringBuilder.append("%.2f".format(hitRate())) 31 | stringBuilder.append(" | 非命中平均耗时: ") 32 | stringBuilder.append("%.2f".format(averageLoadPenalty() / 1000000)) 33 | stringBuilder.append(" 毫秒") 34 | return stringBuilder.toString() 35 | } 36 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/TestCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.permissions.PermissionDefault 4 | import top.iseason.bukkittemplate.command.CommandNode 5 | 6 | object TestCommand : CommandNode( 7 | name = "test", 8 | description = "测试命令,用于调试以及性能检测", 9 | default = PermissionDefault.OP, 10 | async = true 11 | ) -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/TestMatchCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.config.ItemSettings 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkittemplate.command.CommandNode 8 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 9 | import top.iseason.bukkittemplate.command.Param 10 | import top.iseason.bukkittemplate.command.ParmaException 11 | import top.iseason.bukkittemplate.utils.bukkit.EntityUtils.getHeldItem 12 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 13 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 14 | 15 | object TestMatchCommand : CommandNode( 16 | name = "match", 17 | description = "匹配手上的物品 [count] 次并输出信息", 18 | default = PermissionDefault.OP, 19 | isPlayerOnly = true, 20 | async = true, 21 | params = listOf(Param("[count]", listOf("1", "5", "100"))) 22 | ) { 23 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 24 | val count = params.nextOrDefault(1) 25 | val player = sender as Player 26 | val heldItem = player.getHeldItem() ?: throw ParmaException("请拿着物品") 27 | val m1 = System.currentTimeMillis() 28 | val n1 = System.nanoTime() 29 | var keyPath = "global-setting" 30 | sender.sendColorMessage(Lang.command__test__match_start.formatBy(count)) 31 | repeat(count) { 32 | keyPath = ItemSettings.getMatchedSetting(heldItem).keyPath 33 | } 34 | val m2 = System.currentTimeMillis() 35 | val n2 = System.nanoTime() 36 | sender.sendColorMessage( 37 | Lang.command__test__match.formatBy( 38 | count, 39 | keyPath, 40 | m2 - m1, 41 | n2 - n1 42 | ) 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/TestTryMatchCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.config.ItemSettings 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkittemplate.command.CommandNode 8 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 9 | import top.iseason.bukkittemplate.command.Param 10 | import top.iseason.bukkittemplate.command.ParmaException 11 | import top.iseason.bukkittemplate.utils.bukkit.EntityUtils.getHeldItem 12 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 13 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 14 | 15 | object TestTryMatchCommand : CommandNode( 16 | name = "tryMatch", 17 | description = "尝试以某个配置匹配手上的物品, 输出信息", 18 | default = PermissionDefault.OP, 19 | isPlayerOnly = true, 20 | async = true, 21 | params = listOf( 22 | Param("", suggestRuntime = { ItemSettings.getSettingKeys() }) 23 | ) 24 | ) { 25 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 26 | val key = params.next() 27 | if (key !in ItemSettings.getSettingKeys()) throw ParmaException(Lang.command__test__try_match_not_found) 28 | val setting = ItemSettings.getSetting(key) 29 | val player = sender as Player 30 | val itemStack = player.getHeldItem() ?: throw ParmaException("请拿着物品") 31 | player.sendColorMessage(Lang.command__test__try_match_header.formatBy(key)) 32 | val match = setting.match(itemStack, player) 33 | player.sendColorMessage(Lang.command__test__try_match_result.formatBy(match)) 34 | } 35 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/command/UnBindAllCommand.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.command 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.permissions.PermissionDefault 5 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkit.sakurabind.utils.BindType 8 | import top.iseason.bukkit.sakurabind.utils.MessageTool 9 | import top.iseason.bukkittemplate.command.CommandNode 10 | import top.iseason.bukkittemplate.command.CommandNodeExecutor 11 | import top.iseason.bukkittemplate.command.Param 12 | import top.iseason.bukkittemplate.command.ParamSuggestCache 13 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 14 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 15 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessages 16 | 17 | object UnBindAllCommand : CommandNode( 18 | name = "unBindAll", 19 | description = "解绑定某玩家背包的物品", 20 | default = PermissionDefault.OP, 21 | params = listOf(Param("", suggestRuntime = ParamSuggestCache.playerParam)), 22 | async = true 23 | ) { 24 | override var onExecute: CommandNodeExecutor? = CommandNodeExecutor { params, sender -> 25 | val player = params.getParam(0) 26 | for (itemStack in player.inventory) { 27 | if (itemStack == null) continue 28 | if (itemStack.checkAir()) continue 29 | SakuraBindAPI.unBind(itemStack, BindType.COMMAND_UNBIND_ITEM) 30 | } 31 | player.updateInventory() 32 | if (!params.hasParma("-silent")) 33 | sender.sendColorMessages(Lang.command__unbindAll.formatBy(player.name)) 34 | MessageTool.messageCoolDown(player, Lang.item_unbind_all) 35 | } 36 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/BaseSetting.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config 2 | 3 | import org.bukkit.command.CommandSender 4 | import org.bukkit.entity.HumanEntity 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.config.matcher.BaseMatcher 7 | 8 | /** 9 | * 绑定物品设置 10 | */ 11 | interface BaseSetting { 12 | /** 13 | * 设置的配置路径 14 | */ 15 | val keyPath: String 16 | val matchers: List 17 | 18 | /** 19 | * 匹配物品 20 | * @param item 待匹配的物品 21 | * @return true 匹配成功, false 匹配失败 22 | */ 23 | fun match(item: ItemStack): Boolean 24 | 25 | /** 26 | * 匹配物品 27 | * @param item 待匹配的物品 28 | * @param sender 发送调试信息的人 29 | * @return true 匹配成功, false 匹配失败 30 | */ 31 | fun match(item: ItemStack, sender: CommandSender?): Boolean 32 | 33 | fun getString(key: String): String 34 | 35 | fun getStringList(key: String): List 36 | 37 | fun getIntList(key: String): List 38 | 39 | fun getInt(key: String): Int 40 | 41 | fun getLong(key: String): Long 42 | 43 | fun getDouble(key: String): Double 44 | 45 | /** 46 | * 是否禁止操作 47 | * @param key 获取的配置key 48 | * @param owner 物主的uuid 49 | * @param player 操作的玩家 50 | * @return true 表示禁止操作,false 表示允许操作 51 | */ 52 | fun getBoolean(key: String, owner: String?, player: HumanEntity?): Boolean 53 | 54 | fun clone(): BaseSetting 55 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/BindItemConfig.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config 2 | 3 | 4 | import de.tr7zw.nbtapi.NBT 5 | import org.bukkit.configuration.MemorySection 6 | import org.bukkit.inventory.ItemStack 7 | import top.iseason.bukkittemplate.config.SimpleYAMLConfig 8 | import top.iseason.bukkittemplate.config.annotations.Comment 9 | import top.iseason.bukkittemplate.config.annotations.FilePath 10 | import top.iseason.bukkittemplate.config.annotations.Key 11 | 12 | @FilePath("modules/bind-item.yml") 13 | object BindItemConfig : SimpleYAMLConfig() { 14 | 15 | @Key 16 | @Comment( 17 | "", 18 | "绑定物品,使用一个物品来绑定/解绑物品", 19 | "将绑定物品放在指针上,点击需要绑定的物品完成绑定", 20 | "你需要给物品设置特殊的NBT才能将物品标记为绑定物品, 键是下面配置的 bind.nbt ,值是绑定配置, 无匹配将自动识别", 21 | "", 22 | "给物品下面的配置 unbind.nbt 的nbt 标记物品为解绑物品", 23 | "", 24 | "你可以可以使用命令 '/sakurabind autoBind 键 值' 的方式来快速给物品加上nbt", 25 | "如果是默认配置就是 '/sakurabind autoBind sakura_bind_bind-item' 标记物品为绑定物品, 绑定配置自动识别", 26 | ) 27 | var readme = "" 28 | 29 | @Key 30 | @Comment("", "功能总开关,重启生效") 31 | var enable = false 32 | 33 | @Key 34 | @Comment("", "绑定物品") 35 | var bind: MemorySection? = null 36 | 37 | @Key("bind.nbt") 38 | @Comment("", "识别配置的NBT路径, 值是指定 settings.yml的匹配器键,不存在将自动识别") 39 | var bindNbt = "sakura_bind_bind-item" 40 | 41 | 42 | @Key("bind.chance") 43 | @Comment("", "成功率, 每次绑定都会消耗一个物品") 44 | var bindChance = 100.0 45 | 46 | @Key("bind.chance-nbt") 47 | @Comment("", "成功率,储存在nbt中,不读配置") 48 | var bindChanceNbt = "sakura_bind_bind-item-chance" 49 | 50 | @Key 51 | @Comment("", "解绑物品") 52 | var unbind: MemorySection? = null 53 | 54 | @Key("unbind.nbt") 55 | @Comment("", "识别配置的NBT路径") 56 | var unbindNbt = "sakura_bind_unbind-item" 57 | 58 | @Key("unbind.chance") 59 | @Comment("", "成功率, 每次解绑定都会消耗一个物品") 60 | var unbindChance = 100.0 61 | 62 | @Key("unbind.chance-nbt") 63 | @Comment("", "成功率,储存在nbt中,不读配置") 64 | var unBindChanceNbt = "sakura_bind_unbind-item-chance" 65 | 66 | @Key 67 | @Comment("", "如果绑定/解绑的是可堆叠的物品,那么需要消耗相同数量的物品,否则每次只消耗一个") 68 | var syncAmount = true 69 | 70 | 71 | fun getBind(item: ItemStack): Pair? { 72 | val (settingKey, rate) = NBT.get>(item) { 73 | if (it.hasTag(bindNbt)) { 74 | val chanceStr = it.getString(bindChanceNbt) 75 | val chance = if (chanceStr == "") bindChance 76 | else runCatching { chanceStr.toDouble() }.getOrElse { bindChance } 77 | it.getString(bindNbt) to chance 78 | } else Pair(null, null) 79 | } 80 | if (settingKey == null) return null 81 | return settingKey to rate!! 82 | } 83 | 84 | fun getUnBind(item: ItemStack): Pair? { 85 | val (settingKey, rate) = NBT.get>(item) { 86 | if (it.hasTag(unbindNbt)) { 87 | val chanceStr = it.getString(unBindChanceNbt) 88 | val chance = if (chanceStr == "") unbindChance 89 | else runCatching { chanceStr.toDouble() }.getOrElse { unbindChance } 90 | it.getString(unbindNbt) to chance 91 | } else Pair(null, null) 92 | } 93 | if (settingKey == null) return null 94 | return settingKey to rate!! 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/DefaultItemSetting.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config 2 | 3 | import org.bukkit.configuration.file.YamlConfiguration 4 | import org.bukkit.entity.HumanEntity 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.command.DebugCommand 7 | import top.iseason.bukkittemplate.debug.info 8 | 9 | object DefaultItemSetting : ItemSetting("global-setting", YamlConfiguration()) { 10 | 11 | override fun match(item: ItemStack): Boolean { 12 | return true 13 | } 14 | 15 | override fun getBoolean(key: String, owner: String?, player: HumanEntity?): Boolean { 16 | //权限检查 17 | val result = run { 18 | if (Config.enable_setting_permission_check && player != null) { 19 | if (player.hasPermission("sakurabind.settings.$key.true")) { 20 | return@run true 21 | } else if (player.hasPermission("sakurabind.settings.$key.false")) { 22 | return@run false 23 | } 24 | } 25 | var isOwner = false 26 | if (owner != null) { 27 | isOwner = 28 | (owner == player?.uniqueId.toString()) || player?.hasPermission("sakurabind.bypass.$owner") == true 29 | } 30 | val globalConfig = GlobalSettings.config 31 | if (globalConfig.contains("$key@")) { 32 | return@run if (isOwner) !globalConfig.getBoolean("$key@") 33 | else globalConfig.getBoolean("$key@") 34 | } 35 | return@run globalConfig.getBoolean(key) 36 | } 37 | if (player != null && DebugCommand.debugPlayer.contains(player.uniqueId)) { 38 | info("检查玩家 ${player.name} 配置: $keyPath 配置键: $key 结果: $result") 39 | } 40 | return result 41 | } 42 | 43 | override fun clone(): BaseSetting { 44 | val yamlConfiguration = YamlConfiguration() 45 | yamlConfiguration.set("clone", section) 46 | val reader = yamlConfiguration.saveToString().reader() 47 | val newSection = YamlConfiguration.loadConfiguration(reader).getConfigurationSection("clone")!! 48 | return ItemSetting(keyPath, newSection) 49 | } 50 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/matcher/BaseMatcher.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config.matcher 2 | 3 | import org.bukkit.command.CommandSender 4 | import org.bukkit.inventory.ItemStack 5 | 6 | /** 7 | * 使用原型模式 8 | */ 9 | abstract class BaseMatcher { 10 | /** 11 | * 该配置接受的键 12 | */ 13 | abstract fun getKeys(): Array 14 | 15 | /** 16 | * 从配置中读取数据 17 | */ 18 | abstract fun fromSetting(key: String, any: Any): BaseMatcher? 19 | 20 | /** 21 | * 尝试匹配某个物品 22 | */ 23 | abstract fun tryMatch(item: ItemStack): Boolean 24 | 25 | /** 26 | * Debug 27 | */ 28 | abstract fun onDebug(item: ItemStack, debugHolder: CommandSender) 29 | 30 | open fun onBind(item: ItemStack) {} 31 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/matcher/LoreMatcher.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config.matcher 2 | 3 | import org.bukkit.command.CommandSender 4 | import org.bukkit.inventory.ItemStack 5 | import top.iseason.bukkit.sakurabind.config.Lang 6 | import top.iseason.bukkit.sakurabind.utils.containList 7 | import top.iseason.bukkit.sakurabind.utils.indexOfList 8 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 9 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.noColor 10 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 11 | import java.util.regex.Pattern 12 | 13 | class LoreMatcher : BaseMatcher() { 14 | var stripLoreColor = false 15 | private set 16 | var removeMatched = false 17 | private set 18 | lateinit var lorePatterns: List 19 | private set 20 | 21 | override fun getKeys(): Array = 22 | arrayOf("lore", "lore!", "lore-without-color", "lore-without-color!") 23 | 24 | override fun fromSetting(key: String, any: Any): BaseMatcher? { 25 | val loreMatcher = LoreMatcher() 26 | if (key.endsWith('!')) loreMatcher.removeMatched = true 27 | if (key.length > 5) loreMatcher.stripLoreColor = true 28 | val lore = if (any is String) listOf(any) else any as? Collection ?: return null 29 | loreMatcher.lorePatterns = lore.map { Pattern.compile(it) } 30 | return loreMatcher 31 | } 32 | 33 | override fun tryMatch(item: ItemStack): Boolean { 34 | val meta = item.itemMeta 35 | return with(lorePatterns) { 36 | meta ?: return@with false 37 | if (!meta.hasLore()) return@with false 38 | var lore = meta.lore ?: return@with false 39 | if (stripLoreColor) lore = lore.map { it.noColor() } 40 | containList(lore, lorePatterns) { raw, pattern -> 41 | pattern.matcher(raw).find() 42 | } 43 | } 44 | } 45 | 46 | override fun onDebug(item: ItemStack, debugHolder: CommandSender) { 47 | val meta = item.itemMeta ?: return 48 | with(lorePatterns) { 49 | if (!meta.hasLore()) return@with 50 | var lore = meta.lore ?: return@with 51 | if (stripLoreColor) lore = lore.map { it.noColor() } 52 | val lang = 53 | if (stripLoreColor) Lang.command__test__try_match_lore_strip else Lang.command__test__try_match_lore 54 | val indexOfFirst = indexOfList(lore, lorePatterns) { raw, pattern -> 55 | pattern.matcher(raw).find() 56 | } 57 | val patternIter = lorePatterns.iterator() 58 | var pattern = patternIter.next() 59 | debugHolder.sendColorMessage( 60 | lang.formatBy( 61 | indexOfFirst, 62 | pattern, 63 | if (indexOfFirst < 0) "null" else lore[indexOfFirst], 64 | indexOfFirst >= 0 65 | ) 66 | ) 67 | if (indexOfFirst < 0) return 68 | for (i in (indexOfFirst + 1) until (indexOfFirst + this.size)) { 69 | pattern = patternIter.next() 70 | val result = pattern.matcher(lore[i]).find() 71 | debugHolder.sendColorMessage(lang.formatBy(i, pattern, lore[i], result)) 72 | if (!result) { 73 | break 74 | } 75 | } 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/matcher/MatcherManager.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config.matcher 2 | 3 | import org.bukkit.configuration.ConfigurationSection 4 | import java.util.concurrent.ConcurrentHashMap 5 | 6 | object MatcherManager { 7 | private val matchers = ConcurrentHashMap() 8 | 9 | init { 10 | //注册默认匹配器 11 | addMatcher(NameMatcher()) 12 | addMatcher(TypeMatcher()) 13 | addMatcher(LoreMatcher()) 14 | addMatcher(NBTMatcher()) 15 | } 16 | 17 | fun addMatcher(matcher: BaseMatcher) { 18 | for (key in matcher.getKeys()) { 19 | matchers[key] = matcher 20 | } 21 | } 22 | 23 | fun parseSection(section: ConfigurationSection): List { 24 | return section 25 | .getKeys(false) 26 | .mapNotNull { matchers[it]?.fromSetting(it, section.get(it)!!) } 27 | } 28 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/matcher/NBTMatcher.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config.matcher 2 | 3 | import com.google.gson.Gson 4 | 5 | import org.bukkit.command.CommandSender 6 | import org.bukkit.configuration.ConfigurationSection 7 | import org.bukkit.inventory.ItemStack 8 | import top.iseason.bukkit.sakurabind.config.Lang 9 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.toJson 10 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 11 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 12 | import java.util.regex.Pattern 13 | 14 | class NBTMatcher : BaseMatcher() { 15 | private lateinit var nbt: List, Pattern>> 16 | 17 | override fun getKeys(): Array = arrayOf("nbt") 18 | 19 | override fun fromSetting(key: String, any: Any): BaseMatcher? { 20 | if (any !is ConfigurationSection) return null 21 | val nbtMatcher = NBTMatcher() 22 | nbtMatcher.nbt = any.getKeys(true) 23 | .mapNotNull { 24 | val value = any.get(it) 25 | if (value == null || value is ConfigurationSection) return@mapNotNull null 26 | it.split('.').toTypedArray() to Pattern.compile(value.toString()) 27 | } 28 | return nbtMatcher 29 | } 30 | 31 | override fun tryMatch(item: ItemStack): Boolean { 32 | val json = Gson().fromJson(item.toJson(), Map::class.java) 33 | return nbt.all { 34 | val path = it.first 35 | val pattern = it.second 36 | var temp = json 37 | val size = path.size - 1 38 | var value: String? = null 39 | for (i in 0..size) { 40 | val node = path[i] 41 | val v = temp[node] ?: break 42 | if (i == size) { 43 | value = v.toString() 44 | break 45 | } else { 46 | temp = v as? Map<*, *> ?: break 47 | } 48 | } 49 | var nbtResult = false 50 | if (value != null && pattern.matcher(value).find()) { 51 | nbtResult = true 52 | } 53 | nbtResult 54 | } 55 | 56 | } 57 | 58 | override fun onDebug(item: ItemStack, debugHolder: CommandSender) { 59 | val json = Gson().fromJson(item.toJson(), Map::class.java) 60 | nbt.all { 61 | val path = it.first 62 | val pattern = it.second 63 | var temp = json 64 | val size = path.size - 1 65 | var value: String? = null 66 | for (i in 0..size) { 67 | val node = path[i] 68 | val v = temp[node] ?: break 69 | if (i == size) { 70 | value = v.toString() 71 | break 72 | } else { 73 | temp = v as? Map<*, *> ?: break 74 | } 75 | } 76 | var nbtResult = false 77 | if (value != null && pattern.matcher(value).find()) { 78 | nbtResult = true 79 | } 80 | debugHolder.sendColorMessage( 81 | Lang.command__test__try_match_nbt.formatBy( 82 | pattern, 83 | value, 84 | nbtResult, 85 | path.joinToString(".") 86 | ) 87 | ) 88 | nbtResult 89 | } 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/config/matcher/NameMatcher.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.config.matcher 2 | 3 | import org.bukkit.command.CommandSender 4 | import org.bukkit.inventory.ItemStack 5 | import top.iseason.bukkit.sakurabind.config.Lang 6 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.getDisplayName 7 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 8 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.noColor 9 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 10 | import java.util.regex.Pattern 11 | 12 | class NameMatcher : BaseMatcher() { 13 | private lateinit var pattern: Pattern 14 | private var isStripColor = false 15 | override fun getKeys(): Array = arrayOf("name", "name-without-color") 16 | 17 | override fun fromSetting(key: String, any: Any): BaseMatcher? { 18 | if (any !is String) return null 19 | val nameMatcher = NameMatcher() 20 | if (key == "name-without-color") nameMatcher.isStripColor = true 21 | nameMatcher.pattern = Pattern.compile(any) 22 | return nameMatcher 23 | } 24 | 25 | override fun tryMatch(item: ItemStack): Boolean { 26 | var displayName = item.getDisplayName() ?: return false 27 | if (isStripColor) displayName = displayName.noColor() 28 | return pattern.matcher(displayName).find() 29 | } 30 | 31 | override fun onDebug(item: ItemStack, debugHolder: CommandSender) { 32 | val message = if (!isStripColor) Lang.command__test__try_match_name 33 | else Lang.command__test__try_match_name_strip 34 | debugHolder.sendColorMessage(message.formatBy(pattern, item.getDisplayName() ?: "", tryMatch(item))) 35 | } 36 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/dto/BindLogs.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.dto 2 | 3 | import org.jetbrains.exposed.dao.IntEntity 4 | import org.jetbrains.exposed.dao.IntEntityClass 5 | import org.jetbrains.exposed.dao.id.EntityID 6 | import org.jetbrains.exposed.dao.id.IntIdTable 7 | import org.jetbrains.exposed.sql.javatime.datetime 8 | import top.iseason.bukkit.sakurabind.utils.BindType 9 | 10 | object BindLogs : IntIdTable() { 11 | var uuid = uuid("owner") 12 | var bindType = enumeration("type") 13 | var setting = varchar("setting", 255) 14 | var time = datetime("time") 15 | var attach = text("attach") 16 | 17 | init { 18 | index(false, uuid, bindType) 19 | } 20 | } 21 | 22 | class BindLog(id: EntityID) : IntEntity(id) { 23 | companion object : IntEntityClass(BindLogs) 24 | 25 | var uuid by BindLogs.uuid 26 | var bindType by BindLogs.bindType 27 | var setting by BindLogs.setting 28 | var time by BindLogs.time 29 | var attach by BindLogs.attach 30 | 31 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/dto/PlayerItems.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.dto 2 | 3 | import org.bukkit.inventory.ItemStack 4 | import org.jetbrains.exposed.dao.IntEntity 5 | import org.jetbrains.exposed.dao.IntEntityClass 6 | import org.jetbrains.exposed.dao.id.EntityID 7 | import org.jetbrains.exposed.dao.id.IntIdTable 8 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils 9 | 10 | 11 | object PlayerItems : IntIdTable() { 12 | var uuid = uuid("uuid").index() 13 | var item = blob("item") 14 | } 15 | 16 | class PlayerItem(id: EntityID) : IntEntity(id) { 17 | companion object : IntEntityClass(PlayerItems) 18 | 19 | var uuid by PlayerItems.uuid 20 | var item by PlayerItems.item 21 | 22 | fun getItemStacks(): List { 23 | return ItemUtils.fromByteArrays(item.bytes) 24 | } 25 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/dto/SendBackLogs.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.dto 2 | 3 | import org.jetbrains.exposed.dao.IntEntity 4 | import org.jetbrains.exposed.dao.IntEntityClass 5 | import org.jetbrains.exposed.dao.id.EntityID 6 | import org.jetbrains.exposed.dao.id.IntIdTable 7 | import org.jetbrains.exposed.sql.javatime.datetime 8 | import top.iseason.bukkit.sakurabind.utils.SendBackType 9 | 10 | object SendBackLogs : IntIdTable() { 11 | var uuid = uuid("owner") 12 | var type = enumeration("type") 13 | var dest = varchar("dest", 255) 14 | var time = datetime("time") 15 | var attach = text("attach") 16 | 17 | init { 18 | index(false, uuid, type) 19 | } 20 | } 21 | 22 | class SendBackLog(id: EntityID) : IntEntity(id) { 23 | companion object : IntEntityClass(SendBackLogs) 24 | 25 | var uuid by SendBackLogs.uuid 26 | var type by SendBackLogs.type 27 | var dest by SendBackLogs.dest 28 | var time by SendBackLogs.time 29 | var attach by SendBackLogs.attach 30 | 31 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/dto/UniqueLogs.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.dto 2 | 3 | import org.jetbrains.exposed.dao.IntEntity 4 | import org.jetbrains.exposed.dao.IntEntityClass 5 | import org.jetbrains.exposed.dao.id.EntityID 6 | import org.jetbrains.exposed.dao.id.IntIdTable 7 | import org.jetbrains.exposed.sql.javatime.datetime 8 | 9 | object UniqueLogs : IntIdTable() { 10 | var uuid = char("owner", 36) 11 | var unique = varchar("unique", 255) 12 | var type = varchar("type", 32) 13 | var log = text("log") 14 | var time = datetime("time") 15 | 16 | init { 17 | index(false, uuid, unique) 18 | } 19 | } 20 | 21 | class UniqueLog(id: EntityID) : IntEntity(id) { 22 | companion object : IntEntityClass(UniqueLogs) 23 | 24 | var uuid by UniqueLogs.uuid 25 | var unique by UniqueLogs.unique 26 | var type by UniqueLogs.type 27 | var log by UniqueLogs.log 28 | var time by UniqueLogs.time 29 | 30 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/AuthMeHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import top.iseason.bukkittemplate.hook.BaseHook 4 | 5 | object AuthMeHook : BaseHook("AuthMe") { 6 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/BanItemHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import cc.bukkitPlugin.banitem.api.invGettor.InvGettorManager 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.Inventory 6 | import top.iseason.bukkittemplate.hook.BaseHook 7 | 8 | object BanItemHook : BaseHook("BanItem") { 9 | 10 | fun getModInventories(player: Player): Collection = InvGettorManager.getAllInv(player) 11 | override fun checkHooked() { 12 | try { 13 | Class.forName("cc.bukkitPlugin.banitem.BanItem") 14 | super.checkHooked() 15 | } catch (_: Exception) { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/GermHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import com.germ.germplugin.api.GermSlotAPI 4 | import org.bukkit.Material 5 | import org.bukkit.entity.Player 6 | import org.bukkit.inventory.ItemStack 7 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 8 | import top.iseason.bukkit.sakurabind.command.CallbackCommand 9 | import top.iseason.bukkit.sakurabind.config.ItemSettings 10 | import top.iseason.bukkit.sakurabind.config.Lang 11 | import top.iseason.bukkit.sakurabind.utils.BindType 12 | import top.iseason.bukkit.sakurabind.utils.MessageTool 13 | import top.iseason.bukkittemplate.debug.debug 14 | import top.iseason.bukkittemplate.hook.BaseHook 15 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 16 | import java.util.* 17 | 18 | object GermHook : BaseHook("GermPlugin") { 19 | 20 | fun scanSlots(sendBackMap: MutableMap>, player: Player): Boolean { 21 | var hasFound = false 22 | try { 23 | val allGermSlotIdentity = GermSlotAPI.getAllGermSlotIdentity() 24 | //为了兼容mod,获取到的格子数不一致 25 | for (id in allGermSlotIdentity) { 26 | val item = GermSlotAPI.getItemStackFromIdentity(player, id) 27 | if (item.checkAir()) continue 28 | val owner = SakuraBindAPI.getOwner(item) 29 | val ownerStr = owner?.toString() 30 | val setting = ItemSettings.getSetting(item) 31 | if (owner != null && 32 | setting.getBoolean("auto-unbind.enable", ownerStr, player) && 33 | setting.getBoolean("auto-unbind.onScanner", ownerStr, player) 34 | ) { 35 | debug { "萌芽: 解绑物品 ${item.type}" } 36 | SakuraBindAPI.unBind(item, BindType.SCANNER_UNBIND_ITEM) 37 | MessageTool.messageCoolDown(player, Lang.auto_unbind__onScanner) 38 | continue 39 | } 40 | if (owner != null && owner != player.uniqueId && 41 | (CallbackCommand.isCallback(owner) || setting.getBoolean( 42 | "item.send-back-scanner", 43 | ownerStr, 44 | player 45 | )) 46 | ) { 47 | debug { "萌芽: ${player.name} 的槽中存在绑定物品 ${item.type} 属于 $owner" } 48 | sendBackMap.computeIfAbsent(owner) { mutableListOf() }.add(item) 49 | GermSlotAPI.saveItemStackToIdentity(player, id, ItemStack(Material.AIR)) 50 | hasFound = true 51 | continue 52 | } 53 | if (owner == null && 54 | ((setting.getBoolean("auto-bind.enable", null, player) && setting.getBoolean( 55 | "auto-bind.onScanner", 56 | null, 57 | player 58 | )) || SakuraBindAPI.isAutoBind(item)) 59 | ) { 60 | debug { "萌芽: 绑定物品 ${item.type}" } 61 | MessageTool.bindMessageCoolDown(player, Lang.auto_bind__onScanner, setting, item) 62 | SakuraBindAPI.bind(item, player, type = BindType.SCANNER_BIND_ITEM) 63 | } 64 | } 65 | } catch (_: Exception) { 66 | } 67 | return hasFound 68 | } 69 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/GlobalMarketPlusHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import top.iseason.bukkittemplate.hook.BaseHook 4 | 5 | object GlobalMarketPlusHook : BaseHook("GlobalMarketPlus") -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/HuskSyncHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import net.william278.husksync.event.BukkitSyncCompleteEvent 4 | import org.bukkit.event.EventHandler 5 | import org.bukkit.event.EventPriority 6 | import top.iseason.bukkit.sakurabind.listener.SelectListener 7 | import top.iseason.bukkittemplate.hook.BaseHook 8 | 9 | object HuskSyncHook : BaseHook("HuskSync"), org.bukkit.event.Listener { 10 | override fun checkHooked() { 11 | super.checkHooked() 12 | if (hasHooked) SelectListener.hasSyncPlugin = true 13 | } 14 | 15 | @EventHandler(priority = EventPriority.HIGHEST) 16 | fun onDataSync(event: BukkitSyncCompleteEvent) { 17 | SelectListener.noScanning.remove(event.user.uuid) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/InvSyncHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import com.xbaimiao.invsync.api.events.PlayerDataSyncDoneEvent 4 | import org.bukkit.event.EventHandler 5 | import top.iseason.bukkit.sakurabind.listener.SelectListener 6 | import top.iseason.bukkittemplate.hook.BaseHook 7 | 8 | object InvSyncHook : BaseHook("InvSync"), org.bukkit.event.Listener { 9 | override fun checkHooked() { 10 | super.checkHooked() 11 | if (hasHooked) SelectListener.hasSyncPlugin = true 12 | } 13 | 14 | @EventHandler 15 | fun onDataLoad(event: PlayerDataSyncDoneEvent) { 16 | SelectListener.noScanning.remove(event.player.uniqueId) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/ItemsAdderHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import dev.lone.itemsadder.api.CustomStack 4 | import org.bukkit.command.CommandSender 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkit.sakurabind.config.matcher.BaseMatcher 8 | import top.iseason.bukkittemplate.hook.BaseHook 9 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 10 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 11 | 12 | object ItemsAdderHook : BaseHook("ItemsAdder") { 13 | 14 | fun isItemsAdderItem(item: ItemStack) = CustomStack.byItemStack(item) != null 15 | 16 | fun getItemsAdderId(item: ItemStack): String? = CustomStack.byItemStack(item)?.namespacedID 17 | fun getItemsAdderNamespace(item: ItemStack) = CustomStack.byItemStack(item)?.namespace 18 | 19 | } 20 | 21 | class ItemsAdderMatcher : BaseMatcher() { 22 | private lateinit var ids: Set 23 | private var matchAll = false 24 | private var onlyNamespace = false 25 | override fun getKeys(): Array = arrayOf("itemsadder", "itemsadder-namespace") 26 | 27 | override fun fromSetting(key: String, any: Any): BaseMatcher? { 28 | val itemsAdderMatcher = ItemsAdderMatcher() 29 | if (key == "itemsadder-namespace") itemsAdderMatcher.onlyNamespace = true 30 | if (any is String && any == "all") return itemsAdderMatcher.apply { this.matchAll = true } 31 | if (any !is Collection<*>) return null 32 | itemsAdderMatcher.ids = any.map { it.toString().trim() }.toHashSet() 33 | return itemsAdderMatcher 34 | } 35 | 36 | override fun tryMatch(item: ItemStack): Boolean { 37 | if (matchAll) return ItemsAdderHook.isItemsAdderItem(item) 38 | if (onlyNamespace) return ids.contains(ItemsAdderHook.getItemsAdderNamespace(item)) 39 | return ids.contains(ItemsAdderHook.getItemsAdderId(item)) 40 | } 41 | 42 | override fun onDebug(item: ItemStack, debugHolder: CommandSender) { 43 | val itemsId = 44 | if (onlyNamespace) ItemsAdderHook.getItemsAdderNamespace(item) else ItemsAdderHook.getItemsAdderId(item) 45 | if (matchAll) debugHolder.sendColorMessage( 46 | Lang.command__test__try_match_itemsadder.formatBy("all", itemsId ?: "", tryMatch(item)) 47 | ) 48 | else debugHolder.sendColorMessage( 49 | Lang.command__test__try_match_itemsadder 50 | .formatBy(if (ids.size > 3) "..." else ids.joinToString(), itemsId ?: "", tryMatch(item)) 51 | ) 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/MMOItemsHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import net.Indyuce.mmoitems.MMOItems 4 | import net.Indyuce.mmoitems.api.event.ItemBuildEvent 5 | import org.bukkit.command.CommandSender 6 | import org.bukkit.event.EventHandler 7 | import org.bukkit.inventory.ItemStack 8 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 9 | import top.iseason.bukkit.sakurabind.config.Lang 10 | import top.iseason.bukkit.sakurabind.config.matcher.BaseMatcher 11 | import top.iseason.bukkittemplate.hook.BaseHook 12 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 13 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 14 | 15 | object MMOItemsHook : BaseHook("MMOItems"), org.bukkit.event.Listener { 16 | 17 | @EventHandler 18 | fun onItemBuild(event: ItemBuildEvent) { 19 | val itemStack = event.itemStack 20 | if (!SakuraBindAPI.hasBind(itemStack)) { 21 | return 22 | } 23 | SakuraBindAPI.updateLore(itemStack) 24 | } 25 | 26 | fun isMMOItemsItem(item: ItemStack): Boolean { 27 | if (!hasHooked) return false 28 | return MMOItems.getType(item) != null 29 | } 30 | 31 | /** 32 | * type:id 33 | */ 34 | fun getMMOItemsId(item: ItemStack): String? { 35 | if (!hasHooked) return null 36 | val type = MMOItems.getType(item)?.id ?: return null 37 | val id = MMOItems.getID(item) ?: return null 38 | return "$type:$id" 39 | } 40 | 41 | /** 42 | * type 43 | */ 44 | fun getMMOItemsType(item: ItemStack): String? { 45 | if (!hasHooked) return null 46 | return MMOItems.getType(item)?.id 47 | } 48 | 49 | } 50 | 51 | class MMOItemsMatcher : BaseMatcher() { 52 | private lateinit var ids: Set 53 | private var matchAll = false 54 | private var onlyType = false 55 | override fun getKeys(): Array = arrayOf("mmoitems", "mmoitems-type") 56 | 57 | override fun fromSetting(key: String, any: Any): BaseMatcher? { 58 | val mmoItemsMatcher = MMOItemsMatcher() 59 | if (key == "mmoitems-type") mmoItemsMatcher.onlyType = true 60 | if (any is String && any == "all") return mmoItemsMatcher.apply { this.matchAll = true } 61 | if (any !is Collection<*>) return null 62 | mmoItemsMatcher.ids = any.map { it.toString().trim() }.toHashSet() 63 | return mmoItemsMatcher 64 | } 65 | 66 | override fun tryMatch(item: ItemStack): Boolean { 67 | if (matchAll) return MMOItemsHook.isMMOItemsItem(item) 68 | if (onlyType) return ids.contains(MMOItemsHook.getMMOItemsType(item)) 69 | return ids.contains(MMOItemsHook.getMMOItemsId(item)) 70 | } 71 | 72 | override fun onDebug(item: ItemStack, debugHolder: CommandSender) { 73 | val mmoItemsId = if (onlyType) MMOItemsHook.getMMOItemsType(item) else MMOItemsHook.getMMOItemsId(item) 74 | if (matchAll) debugHolder.sendColorMessage( 75 | Lang.command__test__try_match_mmoitems.formatBy("all", mmoItemsId ?: "", tryMatch(item)) 76 | ) 77 | else debugHolder.sendColorMessage( 78 | Lang.command__test__try_match_mmoitems 79 | .formatBy(if (ids.size > 3) "..." else ids.joinToString(), mmoItemsId ?: "", tryMatch(item)) 80 | ) 81 | } 82 | } 83 | // 84 | //object SakuraBindStat : BooleanStat( 85 | // "SAKURA_BIND", 86 | // Material.REDSTONE_LAMP, 87 | // "樱花绑定", 88 | // arrayOf("SakuraBind 自动绑定开关", "效果就是插入 自动绑定的 NBT", "在config.yml 中配置"), 89 | // arrayOf("all"), 90 | // *emptyArray() 91 | //) { 92 | // override fun whenApplied(item: ItemStackBuilder, data: BooleanData) { 93 | // super.whenApplied(item, data) 94 | // item.addItemTag(ItemTag(Config.auto_bind_nbt, "")) 95 | // } 96 | //} -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/McMMoHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import com.gmail.nossr50.events.skills.repair.McMMOPlayerRepairCheckEvent 4 | import com.gmail.nossr50.events.skills.salvage.McMMOPlayerSalvageCheckEvent 5 | import com.gmail.nossr50.events.skills.unarmed.McMMOPlayerDisarmEvent 6 | import org.bukkit.event.EventHandler 7 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 8 | import top.iseason.bukkit.sakurabind.config.Lang 9 | import top.iseason.bukkit.sakurabind.utils.MessageTool 10 | import top.iseason.bukkittemplate.hook.BaseHook 11 | import top.iseason.bukkittemplate.utils.bukkit.EntityUtils.getHeldItem 12 | 13 | object McMMoHook : BaseHook("mcMMO"), org.bukkit.event.Listener { 14 | 15 | @EventHandler 16 | fun onItemSalvage(event: McMMOPlayerSalvageCheckEvent) { 17 | val itemStack = event.salvageItem 18 | val owner = SakuraBindAPI.getOwner(itemStack) ?: return 19 | val player = event.player 20 | if (SakuraBindAPI.getItemSetting(itemStack).getBoolean("addons.mcmmo-salvage", owner.toString(), player)) { 21 | MessageTool.messageCoolDown(player, Lang.addons__mcmmo_salvage) 22 | event.isCancelled = true 23 | } 24 | } 25 | 26 | @EventHandler 27 | fun onItemRepair(event: McMMOPlayerRepairCheckEvent) { 28 | val itemStack = event.repairedObject 29 | val owner = SakuraBindAPI.getOwner(itemStack) ?: return 30 | val player = event.player 31 | if (SakuraBindAPI.getItemSetting(itemStack).getBoolean("addons.mcmmo-repair", owner.toString(), player)) { 32 | MessageTool.messageCoolDown(player, Lang.addons__mcmmo_repair) 33 | event.isCancelled = true 34 | } 35 | } 36 | 37 | @EventHandler 38 | fun onItemDisarm(event: McMMOPlayerDisarmEvent) { 39 | val player = event.defender 40 | val itemStack = player.getHeldItem() ?: return 41 | val owner = SakuraBindAPI.getOwner(itemStack) ?: return 42 | if (SakuraBindAPI.getItemSetting(itemStack).getBoolean("addons.mcmmo-disarm", owner.toString(), player)) { 43 | MessageTool.messageCoolDown(event.player, Lang.addons__mcmmo_disarm) 44 | event.isCancelled = true 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/OraxenHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import io.th0rgal.oraxen.api.OraxenItems 4 | import org.bukkit.command.CommandSender 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkit.sakurabind.config.matcher.BaseMatcher 8 | import top.iseason.bukkittemplate.hook.BaseHook 9 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 10 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 11 | 12 | object OraxenHook : BaseHook("Oraxen") { 13 | fun isOraxenItem(item: ItemStack) = OraxenItems.getIdByItem(item) != null 14 | fun getOraxenItemId(item: ItemStack): String? = OraxenItems.getIdByItem(item) 15 | } 16 | 17 | class OraxenMatcher : BaseMatcher() { 18 | private lateinit var ids: Set 19 | private var matchAll = false 20 | 21 | override fun getKeys(): Array = arrayOf("oraxen") 22 | 23 | override fun fromSetting(key: String, any: Any): BaseMatcher? { 24 | if (any is String && any == "all") return OraxenMatcher().apply { this.matchAll = true } 25 | if (any !is Collection<*>) return null 26 | val oraxenMatcher = OraxenMatcher() 27 | oraxenMatcher.ids = any.map { it.toString().trim() }.toHashSet() 28 | return oraxenMatcher 29 | } 30 | 31 | override fun tryMatch(item: ItemStack): Boolean { 32 | if (matchAll) return OraxenHook.isOraxenItem(item) 33 | return ids.contains(OraxenHook.getOraxenItemId(item)) 34 | } 35 | 36 | override fun onDebug(item: ItemStack, debugHolder: CommandSender) { 37 | val itemsId = OraxenHook.getOraxenItemId(item) 38 | if (matchAll) debugHolder.sendColorMessage( 39 | Lang.command__test__try_match_oraxen.formatBy("all", itemsId ?: "", tryMatch(item)) 40 | ) 41 | else debugHolder.sendColorMessage( 42 | Lang.command__test__try_match_oraxen 43 | .formatBy(if (ids.size > 3) "..." else ids.joinToString(), itemsId ?: "", tryMatch(item)) 44 | ) 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/PlayerDataSQLHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import cc.bukkitPlugin.pds.events.PlayerDataLoadCompleteEvent 4 | import org.bukkit.event.EventHandler 5 | import org.bukkit.event.EventPriority 6 | import top.iseason.bukkit.sakurabind.listener.SelectListener 7 | import top.iseason.bukkittemplate.hook.BaseHook 8 | 9 | object PlayerDataSQLHook : BaseHook("PlayerDataSQL"), org.bukkit.event.Listener { 10 | override fun checkHooked() { 11 | super.checkHooked() 12 | if (hasHooked) SelectListener.hasSyncPlugin = true 13 | } 14 | 15 | @EventHandler(priority = EventPriority.HIGHEST) 16 | fun onDataSync(event: PlayerDataLoadCompleteEvent) { 17 | SelectListener.noScanning.remove(event.player.uniqueId) 18 | } 19 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/hook/SweetMailHook.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.hook 2 | 3 | import top.iseason.bukkittemplate.hook.BaseHook 4 | 5 | object SweetMailHook : BaseHook("SweetMail") -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/BindActionListener.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.event.EventHandler 5 | import org.bukkit.event.EventPriority 6 | import top.iseason.bukkit.sakurabind.event.BlockBindEvent 7 | import top.iseason.bukkit.sakurabind.event.ItemBindEvent 8 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.getDisplayName 9 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 10 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessages 11 | 12 | object BindActionListener : org.bukkit.event.Listener { 13 | 14 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 15 | fun onItemBindEvent(event: ItemBindEvent) { 16 | val setting = event.setting 17 | val stringList = setting.getStringList("on-bind.item-msg") 18 | if (stringList.isEmpty()) return 19 | val item = event.item 20 | val type = item.type 21 | val amount = item.amount 22 | val subId = item.data?.data ?: 0.toByte() 23 | val displayName = item.getDisplayName() ?: "" 24 | val map = stringList.map { it.formatBy(type, displayName, amount, item.type.id, subId) } 25 | (Bukkit.getPlayer(event.owner) ?: Bukkit.getConsoleSender()).sendColorMessages(map) 26 | } 27 | 28 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 29 | fun onBlockBindEvent(event: BlockBindEvent) { 30 | val setting = event.setting 31 | val stringList = setting.getStringList("on-bind.block-msg") 32 | if (stringList.isEmpty()) return 33 | val block = event.block 34 | val type = block.type 35 | val world = block.world.name 36 | val x = block.x 37 | val y = block.y 38 | val z = block.z 39 | 40 | val map = stringList.map { it.formatBy(type, world, x, y, z, block.data) } 41 | (Bukkit.getPlayer(event.owner) ?: Bukkit.getConsoleSender()).sendColorMessages(map) 42 | } 43 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/BlockListener1132.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import org.bukkit.event.EventHandler 4 | import org.bukkit.event.EventPriority 5 | import org.bukkit.event.Listener 6 | import org.bukkit.event.block.BlockPhysicsEvent 7 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 8 | import top.iseason.bukkit.sakurabind.cache.BlockCache 9 | import top.iseason.bukkit.sakurabind.utils.BindType 10 | import top.iseason.bukkittemplate.debug.debug 11 | 12 | object BlockListener1132 : Listener { 13 | 14 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 15 | fun onBlockPhysicsEvent(event: BlockPhysicsEvent) { 16 | val sourceBlock = event.sourceBlock 17 | if (!sourceBlock.isEmpty) return 18 | val blockToString = BlockCache.blockToString(sourceBlock) 19 | val blockInfo = BlockCache.getBlockInfo(blockToString) ?: return 20 | SakuraBindAPI.unbindBlock(sourceBlock, type = BindType.BLOCK_TO_ITEM_UNBIND) 21 | BlockCache.addBreakingCache(blockToString, blockInfo) 22 | debug { "2 add temp $blockToString to ${blockInfo.setting.keyPath} for ${blockInfo.owner}" } 23 | } 24 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/ItemListener16.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.entity.ThrowableProjectile 5 | import org.bukkit.event.EventHandler 6 | import org.bukkit.event.EventPriority 7 | import org.bukkit.event.Listener 8 | import org.bukkit.event.entity.ProjectileLaunchEvent 9 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 10 | import top.iseason.bukkit.sakurabind.config.Config 11 | import top.iseason.bukkit.sakurabind.config.ItemSettings 12 | import top.iseason.bukkit.sakurabind.config.Lang 13 | import top.iseason.bukkit.sakurabind.task.DropItemList 14 | import top.iseason.bukkit.sakurabind.utils.MessageTool 15 | 16 | object ItemListener16 : Listener { 17 | /** 18 | * 禁止弹射物射出 19 | */ 20 | @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) 21 | fun onProjectileLaunchEvent(event: ProjectileLaunchEvent) { 22 | val entity = event.entity 23 | val player = entity.shooter as? Player ?: return 24 | if (Config.checkByPass(player)) return 25 | val throwable = entity as? ThrowableProjectile ?: return 26 | val item = try { 27 | throwable.item 28 | } catch (_: Throwable) { 29 | return 30 | } 31 | val owner = SakuraBindAPI.getOwner(item) ?: return 32 | val setting = ItemSettings.getSetting(item) 33 | if (setting.getBoolean("item-deny.throw", owner.toString(), player)) { 34 | event.isCancelled = true 35 | MessageTool.denyMessageCoolDown( 36 | player, 37 | Lang.item__deny_throw, 38 | ItemSettings.getSetting(item), 39 | item 40 | ) 41 | } else { 42 | val delay = setting.getInt("item.send-back-delay") 43 | DropItemList.putThrowableItem(entity, owner, delay) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/ItemListener194.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import org.bukkit.event.EventHandler 4 | import org.bukkit.event.EventPriority 5 | import org.bukkit.event.Listener 6 | import org.bukkit.event.inventory.PrepareAnvilEvent 7 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 8 | import top.iseason.bukkit.sakurabind.config.Config 9 | import top.iseason.bukkit.sakurabind.config.ItemSettings 10 | import top.iseason.bukkit.sakurabind.config.Lang 11 | import top.iseason.bukkit.sakurabind.utils.MessageTool 12 | 13 | object ItemListener194 : Listener { 14 | 15 | /** 16 | * 禁止用于铁砧 17 | */ 18 | @EventHandler(priority = EventPriority.MONITOR) 19 | fun onPrepareAnvilEvent(event: PrepareAnvilEvent) { 20 | if (Config.checkByPass(event.view.player)) return 21 | val item1 = event.inventory.getItem(0) 22 | val item2 = event.inventory.getItem(1) 23 | val player = event.view.player 24 | if (SakuraBindAPI.checkDenyBySetting(item1, player, "item-deny.anvil")) { 25 | event.result = null 26 | MessageTool.denyMessageCoolDown(player, Lang.item__deny_anvil, ItemSettings.getSetting(item1!!), item1) 27 | } else if (SakuraBindAPI.checkDenyBySetting(item2, player, "item-deny.anvil")) { 28 | event.result = null 29 | MessageTool.denyMessageCoolDown(player, Lang.item__deny_anvil, ItemSettings.getSetting(item2!!), item2) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/LegacyPickupItemListener.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import org.bukkit.event.EventHandler 4 | import org.bukkit.event.EventPriority 5 | import org.bukkit.event.Listener 6 | import org.bukkit.event.player.PlayerPickupItemEvent 7 | import top.iseason.bukkit.sakurabind.config.Config 8 | 9 | /* 10 | * 1.12以下的版本检测物品捡起 11 | * */ 12 | object LegacyPickupItemListener : Listener { 13 | 14 | /** 15 | * 捡起检测 16 | */ 17 | @EventHandler(ignoreCancelled = true) 18 | fun onPlayerPickupItemEvent(event: PlayerPickupItemEvent) { 19 | PickupItemListener.playerPickupItem(event.player, event.item, event) 20 | } 21 | 22 | /** 23 | * 自动绑定, 捡起物品时绑定 24 | */ 25 | @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) 26 | fun autoBindPlayerPickupItemEvent(event: PlayerPickupItemEvent) { 27 | val player = event.player 28 | if (Config.checkByPass(player)) return 29 | val item = event.item.itemStack 30 | PickupItemListener.checkAutoBind(player, item) 31 | } 32 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/LoginAuthMeListener.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import fr.xephi.authme.events.LoginEvent 4 | import org.bukkit.event.EventHandler 5 | import org.bukkit.event.EventPriority 6 | import org.bukkit.event.Listener 7 | 8 | object LoginAuthMeListener : Listener { 9 | 10 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 11 | fun onPlayerLoginEvent(event: LoginEvent) { 12 | ItemListener.onLogin(event.player) 13 | } 14 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/LoginListener.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import org.bukkit.event.EventHandler 4 | import org.bukkit.event.EventPriority 5 | import org.bukkit.event.Listener 6 | import org.bukkit.event.player.PlayerLoginEvent 7 | 8 | object LoginListener : Listener { 9 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 10 | fun onPlayerLoginEvent(event: PlayerLoginEvent) { 11 | ItemListener.onLogin(event.player) 12 | } 13 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/listener/PaperListener.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.listener 2 | 3 | import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent 4 | import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.* 5 | 6 | import org.bukkit.entity.LivingEntity 7 | import org.bukkit.event.EventHandler 8 | import org.bukkit.event.Listener 9 | import org.bukkit.inventory.ItemStack 10 | import top.iseason.bukkit.sakurabind.SakuraBindAPI 11 | import top.iseason.bukkit.sakurabind.config.Config 12 | import top.iseason.bukkit.sakurabind.config.ItemSettings 13 | import top.iseason.bukkit.sakurabind.config.Lang 14 | import top.iseason.bukkit.sakurabind.utils.BindType 15 | import top.iseason.bukkit.sakurabind.utils.MessageTool 16 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.checkAir 17 | 18 | object PaperListener : Listener { 19 | 20 | fun isPaper() = 21 | try { 22 | Class.forName("com.destroystokyo.paper.event.player.PlayerArmorChangeEvent") 23 | true 24 | } catch (_: Exception) { 25 | false 26 | } 27 | 28 | @EventHandler(ignoreCancelled = true) 29 | fun onArmorChange(event: PlayerArmorChangeEvent) { 30 | val item = event.newItem 31 | if (item == null || item.checkAir()) return 32 | if (Config.checkByPass(event.player)) return 33 | val owner = SakuraBindAPI.getOwner(item)?.toString() 34 | val player = event.player 35 | val setting = ItemSettings.getSetting(item) 36 | if (owner == null) { 37 | if (setting.getBoolean("auto-bind.enable", null, player) && 38 | (setting.getBoolean("auto-bind.onEquipWear", null, player) || 39 | SakuraBindAPI.isAutoBind(item)) 40 | ) { 41 | SakuraBindAPI.bind(item, player, type = BindType.EQUIP_BIND_ITEM) 42 | setEquip(event.slotType, player, item) 43 | MessageTool.messageCoolDown(player, Lang.auto_bind__onEquiped) 44 | } 45 | } else { 46 | if (setting.getBoolean("auto-unbind.enable", owner, player) && 47 | setting.getBoolean("auto-unbind.onEquipWear", owner, player) 48 | ) { 49 | SakuraBindAPI.unBind(item, type = BindType.EQUIP_UNBIND_ITEM) 50 | setEquip(event.slotType, player, item) 51 | MessageTool.messageCoolDown(player, Lang.auto_unbind__onEquiped) 52 | } 53 | } 54 | } 55 | 56 | private fun setEquip(slotType: PlayerArmorChangeEvent.SlotType, player: LivingEntity, item: ItemStack) { 57 | when (slotType) { 58 | HEAD -> player.equipment?.helmet = item 59 | CHEST -> player.equipment?.chestplate = item 60 | LEGS -> player.equipment?.leggings = item 61 | FEET -> player.equipment?.boots = item 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/pickers/DataBasePicker.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.pickers 2 | 3 | import org.bukkit.OfflinePlayer 4 | import org.bukkit.inventory.ItemStack 5 | import top.iseason.bukkit.sakurabind.config.Config 6 | import top.iseason.bukkit.sakurabind.config.SendBackLogger 7 | import top.iseason.bukkit.sakurabind.task.DelaySender 8 | import top.iseason.bukkit.sakurabind.utils.SendBackType 9 | import top.iseason.bukkittemplate.config.DatabaseConfig 10 | import java.util.* 11 | 12 | object DataBasePicker : BasePicker("database") { 13 | 14 | override fun pickup(uuid: UUID, items: Array, type: SendBackType, notify: Boolean): Array? { 15 | if (!Config.send_back_database || !DatabaseConfig.isConnected) return null 16 | DelaySender.sendItem(uuid, items) 17 | SendBackLogger.log(uuid, type, name, items) 18 | return emptyArray() 19 | } 20 | 21 | override fun pickup( 22 | player: OfflinePlayer, 23 | items: Array, 24 | type: SendBackType, 25 | notify: Boolean 26 | ): Array? { 27 | return pickup(player.uniqueId, items, type, notify) 28 | } 29 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/pickers/EnderChestPicker.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.pickers 2 | 3 | import org.bukkit.OfflinePlayer 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkit.sakurabind.config.SendBackLogger 8 | import top.iseason.bukkit.sakurabind.listener.SelectListener 9 | import top.iseason.bukkit.sakurabind.utils.MessageTool 10 | import top.iseason.bukkit.sakurabind.utils.SendBackType 11 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 12 | import java.util.* 13 | 14 | object EnderChestPicker : BasePicker("ender-chest") { 15 | 16 | override fun pickup(uuid: UUID, items: Array, type: SendBackType, notify: Boolean): Array? { 17 | return null 18 | } 19 | 20 | override fun pickup( 21 | player: OfflinePlayer, 22 | items: Array, 23 | type: SendBackType, 24 | notify: Boolean 25 | ): Array? { 26 | if (player !is Player) { 27 | return null 28 | } 29 | if (SelectListener.noScanning.contains(player.uniqueId)) return null 30 | var count = 0 31 | val add = ArrayList() 32 | val remain = ArrayList() 33 | val inventory = player.enderChest 34 | for (item in items) { 35 | val rawAmount = item.amount 36 | val addItem = inventory.addItem(item) 37 | if (addItem.isEmpty) {//全部放下 38 | count += rawAmount 39 | val clone = item.clone() 40 | clone.amount = rawAmount 41 | add.add(clone) 42 | } else { //放不下了 43 | val next = addItem.iterator().next() 44 | val value = next.value 45 | if (value.amount == rawAmount) { //全部放不下 46 | remain.add(item) 47 | } else { //放下部分 48 | remain.add(value) 49 | val addAmount = rawAmount - value.amount 50 | val clone = item.clone() 51 | clone.amount = addAmount 52 | add.add(clone) 53 | count += addAmount 54 | } 55 | } 56 | } 57 | 58 | SendBackLogger.log(player.uniqueId, type, name, add) 59 | if (remain.isEmpty) { 60 | if (notify) MessageTool.sendNormal(player, Lang.send_back__ender_chest_all.formatBy(count)) 61 | return emptyArray() 62 | } else if (count > 0 && notify) 63 | MessageTool.sendNormal(player, Lang.send_back__ender_chest_half.formatBy(count)) 64 | return remain.toTypedArray() 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/pickers/GlobalMarketPlusPicker.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.pickers 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.OfflinePlayer 5 | import org.bukkit.entity.Player 6 | import org.bukkit.inventory.ItemStack 7 | import studio.trc.bukkit.globalmarketplus.api.Mailbox 8 | import studio.trc.bukkit.globalmarketplus.api.Merchant 9 | import studio.trc.bukkit.globalmarketplus.mailbox.ItemMailType 10 | import top.iseason.bukkit.sakurabind.config.Config 11 | import top.iseason.bukkit.sakurabind.config.Lang 12 | import top.iseason.bukkit.sakurabind.config.SendBackLogger 13 | import top.iseason.bukkit.sakurabind.hook.GlobalMarketPlusHook 14 | import top.iseason.bukkit.sakurabind.utils.MessageTool 15 | import top.iseason.bukkit.sakurabind.utils.SendBackType 16 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 17 | import java.util.* 18 | import java.util.concurrent.ConcurrentHashMap 19 | 20 | object GlobalMarketPlusPicker : BasePicker("GlobalMarketPlus") { 21 | private val map = ConcurrentHashMap>() 22 | 23 | override fun pickup( 24 | uuid: UUID, 25 | items: Array, 26 | type: SendBackType, 27 | notify: Boolean 28 | ): Array? { 29 | if (!GlobalMarketPlusHook.hasHooked) return items 30 | addCache(map, uuid, type, items, GlobalMarketPlusPicker::sendBack) 31 | return emptyArray() 32 | } 33 | 34 | override fun pickup( 35 | player: OfflinePlayer, 36 | items: Array, 37 | type: SendBackType, 38 | notify: Boolean 39 | ): Array? { 40 | if (!GlobalMarketPlusHook.hasHooked) return items 41 | return pickup(player.uniqueId, items, type, notify) 42 | } 43 | 44 | fun sendBack(uuid: UUID, type: SendBackType) { 45 | var temp = sendBack0(uuid, type) ?: return 46 | continuePickup(GlobalMarketPlusPicker, uuid, type, temp) 47 | } 48 | 49 | fun sendBack0(uuid: UUID, type: SendBackType): Array? { 50 | val mailbox = Mailbox.getMailbox(uuid) 51 | val senderName = Config.market_sender_name 52 | val seconds = Config.market_sender_time 53 | val time = System.currentTimeMillis() 54 | val expire = if (seconds <= 0) -1L else (time + seconds * 1000) 55 | // 邮箱上限 56 | val mailQuantityLimit = Merchant.getMerchant(uuid).group.mailQuantityLimit 57 | 58 | val items = map.remove(uuid) ?: return null 59 | var mail: List = items 60 | var remaining: List = ArrayList() 61 | if (mailQuantityLimit > 0) { 62 | // 剩余空间 63 | val size = mailQuantityLimit - mailbox.itemMails.size 64 | // 没空间 65 | if (size <= 0) return items.toTypedArray() 66 | mail = items.take(size) 67 | val mailSize = mail.size 68 | if (mailSize < items.size) 69 | remaining = items.drop(mailSize) 70 | } 71 | var count = 0 72 | val player = Bukkit.getPlayer(uuid) ?: Bukkit.getOfflinePlayer(uuid) 73 | val name = player.name 74 | 75 | for (stack in mail) { 76 | count += stack.amount 77 | mailbox.addMail( 78 | uuid, 79 | name, 80 | ItemMailType.OTHER_SOURCE, 81 | time, 82 | expire, 83 | stack, 84 | null, 85 | senderName, 86 | stack.amount 87 | ) 88 | } 89 | if (player is Player) { 90 | MessageTool.sendNormal(player, Lang.send_back__global_market_plus.formatBy(count)) 91 | } 92 | SendBackLogger.log(uuid, type, GlobalMarketPlusPicker.name, mail) 93 | return remaining.toTypedArray() 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/pickers/PlayerInvPicker.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.pickers 2 | 3 | import org.bukkit.OfflinePlayer 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.config.Lang 7 | import top.iseason.bukkit.sakurabind.config.SendBackLogger 8 | import top.iseason.bukkit.sakurabind.listener.SelectListener 9 | import top.iseason.bukkit.sakurabind.utils.MessageTool 10 | import top.iseason.bukkit.sakurabind.utils.SendBackType 11 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 12 | import java.util.* 13 | 14 | object PlayerInvPicker : BasePicker("player") { 15 | 16 | override fun pickup(uuid: UUID, items: Array, type: SendBackType, notify: Boolean): Array? { 17 | return null 18 | } 19 | 20 | override fun pickup( 21 | player: OfflinePlayer, 22 | items: Array, 23 | type: SendBackType, 24 | notify: Boolean 25 | ): Array? { 26 | if (!player.isOnline) return null 27 | if (player !is Player) { 28 | return null 29 | } 30 | if (player.isDead) return null 31 | if (SelectListener.noScanning.contains(player.uniqueId)) return null 32 | var count = 0 33 | val add = ArrayList() 34 | val remain = ArrayList() 35 | val inventory = player.inventory 36 | for (item in items) { 37 | val rawAmount = item.amount 38 | val addItem = inventory.addItem(item) 39 | if (addItem.isEmpty) {//全部放下 40 | count += rawAmount 41 | val clone = item.clone() 42 | clone.amount = rawAmount 43 | add.add(clone) 44 | } else { //放不下了 45 | val next = addItem.iterator().next() 46 | val value = next.value 47 | if (value.amount == rawAmount) { //全部放不下 48 | remain.add(item) 49 | } else { //放下部分 50 | remain.add(value) 51 | val addAmount = rawAmount - value.amount 52 | val clone = item.clone() 53 | clone.amount = addAmount 54 | add.add(clone) 55 | count += addAmount 56 | } 57 | } 58 | } 59 | SendBackLogger.log(player.uniqueId, type, name, add) 60 | if (remain.isEmpty) { 61 | if (notify) MessageTool.sendNormal(player, Lang.send_back__player_all.formatBy(count)) 62 | return emptyArray() 63 | } else if (count > 0 && notify) 64 | MessageTool.sendNormal(player, Lang.send_back__player_half.formatBy(count)) 65 | return remain.toTypedArray() 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/pickers/SweetMailPicker.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.pickers 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.OfflinePlayer 5 | import org.bukkit.inventory.ItemStack 6 | import top.iseason.bukkit.sakurabind.config.Config 7 | import top.iseason.bukkit.sakurabind.config.SendBackLogger 8 | import top.iseason.bukkit.sakurabind.hook.SweetMailHook 9 | import top.iseason.bukkit.sakurabind.utils.SendBackType 10 | import top.mrxiaom.sweetmail.IMail 11 | import top.mrxiaom.sweetmail.SweetMail 12 | import top.mrxiaom.sweetmail.attachments.AttachmentItem 13 | import java.util.* 14 | import java.util.concurrent.ConcurrentHashMap 15 | 16 | object SweetMailPicker : BasePicker("SweetMail") { 17 | 18 | private val map = ConcurrentHashMap>() 19 | 20 | override fun pickup( 21 | uuid: UUID, 22 | items: Array, 23 | type: SendBackType, 24 | notify: Boolean 25 | ): Array? { 26 | if (!SweetMailHook.hasHooked) return null 27 | addCache(map, uuid, type, items, SweetMailPicker::sendBack) 28 | return emptyArray() 29 | } 30 | 31 | fun sendBack(uuid: UUID, type: SendBackType) { 32 | val items = map.remove(uuid) ?: return 33 | val sender = if (SweetMail.getInstance().isOnlineMode) { 34 | uuid.toString() 35 | } else Bukkit.getOfflinePlayer(uuid).name 36 | val mail = IMail.api() 37 | .createSystemMail(Config.sweetMailSender) 38 | .setIcon(Config.sweetMailIcon) // 设置图标,详见源码注释 39 | .setTitle(Config.sweetMailTitle) 40 | .addContent(Config.sweetMailContent) 41 | .setAttachments(items.map { AttachmentItem.build(it) }) 42 | .setReceiver(sender) 43 | val sweetMailExpire = Config.sweetMailExpire 44 | if (sweetMailExpire > 0) 45 | mail.outdateTime = System.currentTimeMillis() + sweetMailExpire * 1000L 46 | val send = mail.send() 47 | if (!send.ok()) { 48 | continuePickup(SweetMailPicker, uuid, type, items.toTypedArray()) 49 | } else { 50 | SendBackLogger.log(uuid, type, name, items) 51 | } 52 | } 53 | 54 | override fun pickup( 55 | player: OfflinePlayer, 56 | items: Array, 57 | type: SendBackType, 58 | notify: Boolean 59 | ): Array? { 60 | if (!SweetMailHook.hasHooked) return null 61 | return pickup(player.uniqueId, items, type, notify) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/task/DelaySender.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.task 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.inventory.ItemStack 5 | import org.bukkit.scheduler.BukkitRunnable 6 | import org.jetbrains.exposed.sql.statements.api.ExposedBlob 7 | import top.iseason.bukkit.sakurabind.config.Lang 8 | import top.iseason.bukkit.sakurabind.dto.PlayerItem 9 | import top.iseason.bukkittemplate.BukkitTemplate 10 | import top.iseason.bukkittemplate.config.DatabaseConfig 11 | import top.iseason.bukkittemplate.config.dbTransaction 12 | import top.iseason.bukkittemplate.debug.warn 13 | import top.iseason.bukkittemplate.utils.bukkit.ItemUtils.toByteArray 14 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.formatBy 15 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 16 | import top.iseason.bukkittemplate.utils.other.runAsync 17 | import java.util.* 18 | import java.util.concurrent.ConcurrentHashMap 19 | 20 | class DelaySender private constructor(private val uuid: UUID) : BukkitRunnable() { 21 | 22 | private val inv = Bukkit.createInventory(null, 36) 23 | 24 | override fun run() { 25 | remove(uuid) 26 | sendItem(false) 27 | } 28 | 29 | @Synchronized 30 | private fun addItem(items: Array) { 31 | val addItem = inv.addItem(*items) 32 | //缓存满了 33 | if (addItem.isNotEmpty()) { 34 | sendItem(true) 35 | addItem(addItem.values.toTypedArray()) 36 | return 37 | } 38 | } 39 | 40 | override fun cancel() { 41 | super.cancel() 42 | remove(uuid) 43 | sendItem(false) 44 | } 45 | 46 | private fun sendItem(async: Boolean) { 47 | val itemStacks = inv.filterNotNull() 48 | inv.clear() 49 | if (DatabaseConfig.isConnected) { 50 | if (async) runAsync { 51 | sendToDataBase(uuid, itemStacks) 52 | } else 53 | sendToDataBase(uuid, itemStacks) 54 | } else { 55 | warn("数据库未启用,无法发送暂存箱子!") 56 | } 57 | Bukkit.getPlayer(uuid)?.let { 58 | val count = itemStacks.sumOf { it.amount } 59 | it.sendColorMessage(Lang.send_back__database_all.formatBy(count)) 60 | } 61 | } 62 | 63 | companion object { 64 | private val map = ConcurrentHashMap() 65 | private val plugin = BukkitTemplate.getPlugin() 66 | 67 | fun sendItem(uuid: UUID, items: Array) { 68 | var sender = map[uuid] 69 | if (!plugin.isEnabled) { 70 | if (sender == null) sender = DelaySender(uuid) 71 | sender.addItem(items) 72 | sender.sendItem(false) 73 | } else if (sender == null) { 74 | sender = DelaySender(uuid) 75 | map[uuid] = sender 76 | sender.addItem(items) 77 | sender.runTaskLaterAsynchronously(plugin, 60) 78 | } else { 79 | sender.addItem(items) 80 | } 81 | } 82 | 83 | fun remove(uuid: UUID) = map.remove(uuid) 84 | 85 | fun sendToDataBase(uid: UUID, items: List) { 86 | dbTransaction { 87 | PlayerItem.new { 88 | this.uuid = uid 89 | this.item = ExposedBlob(items.toByteArray()) 90 | } 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/task/EntityRemoveQueue.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.task 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.entity.Entity 5 | import org.bukkit.scheduler.BukkitRunnable 6 | import java.lang.reflect.Method 7 | import java.util.concurrent.ConcurrentHashMap 8 | 9 | object EntityRemoveQueue : BukkitRunnable() { 10 | private val isRemoving = ConcurrentHashMap.newKeySet() 11 | private val asyncTpMethod: Method? = 12 | runCatching { Entity::class.java.getMethod("teleportAsync", Location::class.java) }.getOrNull() 13 | 14 | /** 15 | * 同步删除实体,存在一定延迟 16 | */ 17 | fun syncRemove(item: Entity) { 18 | if (item.isDead) return 19 | if (isRemoving.contains(item)) return 20 | // 为了兼容尽可能多的mod服务端和游戏版本 21 | // 只能让它传送到玩家碰不到的地方,再在事件结束后删除 22 | val location = item.location 23 | location.y = Short.MIN_VALUE.toDouble() 24 | if (asyncTpMethod != null) 25 | asyncTpMethod.invoke(item, location) 26 | else 27 | item.teleport(location) 28 | isRemoving.add(item) 29 | } 30 | 31 | fun hide(item: Entity) { 32 | val location = item.location 33 | location.y = Short.MAX_VALUE.toDouble() 34 | if (asyncTpMethod != null) 35 | asyncTpMethod.invoke(item, location) 36 | else 37 | item.teleport(location) 38 | } 39 | 40 | /** 41 | * 实体是否被标记删除 42 | */ 43 | fun isRemoved(entity: Entity) = entity.isDead || isRemoving.contains(entity) 44 | 45 | override fun run() { 46 | if (isRemoving.isEmpty()) { 47 | return 48 | } 49 | for (entity in isRemoving) { 50 | entity.remove() 51 | } 52 | isRemoving.clear() 53 | } 54 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/task/FallingList.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.task 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.entity.Entity 5 | import java.util.concurrent.ConcurrentHashMap 6 | 7 | object FallingList { 8 | private val falling = ConcurrentHashMap() 9 | 10 | fun check() { 11 | if (falling.isEmpty()) return 12 | val iterator = falling.iterator() 13 | while (iterator.hasNext()) { 14 | val (_, next) = iterator.next() 15 | if (next.isDead) { 16 | iterator.remove() 17 | continue 18 | } 19 | } 20 | } 21 | 22 | fun addFalling(falling: Entity) { 23 | this.falling[locationToString(falling.location)] = falling 24 | } 25 | 26 | fun findFalling(str: String) = falling[str] 27 | fun findFalling(location: Location) = falling[locationToString(location)] 28 | 29 | fun locationToString(location: Location) = 30 | "${location.world?.name},${location.blockX},${location.blockY},${location.blockZ}" 31 | 32 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/utils/BindType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.utils 2 | 3 | enum class BindType(var description: String) { 4 | 5 | UNKNOWN_BIND_ITEM("未知的方式绑定物品"), 6 | UNKNOWN_UNBIND_ITEM("未知的方式解绑物品"), 7 | UNKNOWN_BIND_BLOCK("未知的方式绑定方块"), 8 | UNKNOWN_UNBIND_BLOCK("未知的方式解绑方块"), 9 | UNKNOWN_BIND_ENTITY("未知的方式绑定实体"), 10 | UNKNOWN_UNBIND_ENTITY("未知的方式解绑实体"), 11 | 12 | API_BIND_ITEM("API绑定物品"), 13 | API_UNBIND_ITEM("API解绑物品"), 14 | API_BIND_BLOCK("API绑定方块"), 15 | API_UNBIND_BLOCK("API解绑方块"), 16 | API_BIND_ENTITY("API绑定实体"), 17 | API_UNBIND_ENTITY("API解绑实体"), 18 | 19 | COMMAND_BIND_ITEM("命令绑定物品"), 20 | COMMAND_UNBIND_ITEM("命令解绑物品"), 21 | COMMAND_BIND_BLOCK("命令绑定方块"), 22 | COMMAND_UNBIND_BLOCK("命令解绑方块"), 23 | COMMAND_BIND_ENTITY("命令绑定实体"), 24 | COMMAND_UNBIND_ENTITY("命令解绑实体"), 25 | 26 | SCANNER_BIND_ITEM("扫描器绑定物品"), 27 | SCANNER_UNBIND_ITEM("扫描器解绑物品"), 28 | 29 | CLICK_BIND_ITEM("点击物品绑定物品"), 30 | CLICK_UNBIND_ITEM("点击物品解绑物品"), 31 | 32 | PICKUP_BIND_ITEM("捡起物品绑定物品"), 33 | PICKUP_UNBIND_ITEM("捡起物品解绑物品"), 34 | 35 | DROP_BIND_ITEM("丢弃物品绑定物品"), 36 | DROP_UNBIND_ITEM("丢弃物品解绑物品"), 37 | 38 | ITEM_TO_BLOCK_BIND("物品转为方块绑定"), 39 | ITEM_TO_BLOCK_UNBIND("物品转为方块解绑"), 40 | BLOCK_TO_ENTITY_BIND("方块转为实体绑定"), 41 | BLOCK_TO_ENTITY_UNBIND("方块转为实体解绑"), 42 | BLOCK_MOVE_BIND("方块移动绑定"), 43 | BLOCK_MOVE_UNBIND("方块移动解绑"), 44 | ENTITY_TO_ITEM_BIND("实体转为物品绑定"), 45 | ENTITY_TO_ITEM_UNBIND("实体转为物品解绑"), 46 | ITEM_TO_ENTITY_BIND("物品转为实体绑定"), 47 | ITEM_TO_ENTITY_UNBIND("物品转为实体解绑"), 48 | BLOCK_TO_ITEM_BIND("方块转为物品绑定"), 49 | BLOCK_TO_ITEM_UNBIND("方块转为物品解绑"), 50 | ENTITY_TO_BLOCK_BIND("实体转为方块绑定"), 51 | ENTITY_TO_BLOCK_UNBIND("实体转为方块解绑"), 52 | 53 | USE_BIND_ITEM("消耗耐久物品绑定物品"), 54 | USE_UNBIND_ITEM("消耗耐久解绑物品"), 55 | 56 | LEFT_BIND_ITEM("拿着物品左键绑定物品"), 57 | LEFT_UNBIND_ITEM("拿着物品左键解绑物品"), 58 | 59 | RIGHT_BIND_ITEM("拿着物品右键绑定物品"), 60 | RIGHT_UNBIND_ITEM("拿着物品右键解绑物品"), 61 | 62 | MIGRATION_FROM_LORE_ITEM("从其他插件lore迁移"), 63 | MIGRATION_FROM_NBT_ITEM("从其他插件NBT迁移"), 64 | 65 | EQUIP_BIND_ITEM("装备物品时绑定"), 66 | EQUIP_UNBIND_ITEM("装备物品时解绑"), 67 | BIND_ITEM_BIND_ITEM("绑定物品功能-绑定物品"), 68 | BIND_ITEM_UNBIND_ITEM("绑定物品功能-解绑物品") 69 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/utils/Defenders.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.utils 2 | 3 | import org.bukkit.entity.LivingEntity 4 | import top.iseason.bukkit.sakurabind.config.BaseSetting 5 | 6 | class Defenders(val target: LivingEntity, val setting: BaseSetting) { 7 | private val sets = LinkedHashSet() 8 | private var iterator: MutableIterator = sets.iterator() 9 | 10 | /** 11 | * 添加肉盾 12 | */ 13 | fun addDefender(defender: LivingEntity) { 14 | if (sets.contains(defender)) return 15 | sets.add(defender) 16 | iterator = sets.iterator() 17 | } 18 | 19 | /** 20 | * 使用轮询的方式来获取 21 | */ 22 | fun getDefender(): LivingEntity? { 23 | var defender: LivingEntity? = null 24 | while (iterator.hasNext()) { 25 | val next = iterator.next() 26 | val l1 = next.location 27 | val l2 = target.location 28 | if (next.isDead || l1.world != l2.world || l1.distance(l2) >= setting.getDouble("entity.defend-distance")) iterator.remove() 29 | defender = next 30 | break 31 | } 32 | if (!iterator.hasNext()) { 33 | iterator = sets.iterator() 34 | } 35 | return defender 36 | } 37 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/utils/ListUtil.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.utils 2 | 3 | /* 4 | * 从List1中移除List2 index to newList 5 | * */ 6 | internal fun removeList( 7 | list1: List, 8 | collection: Collection, 9 | op: (String, T) -> Boolean 10 | ): Pair> { 11 | var index = indexOfList(list1, collection, op) 12 | var toMutableList = list1.toMutableList() 13 | if (index > -1) { 14 | repeat(collection.size) { 15 | toMutableList.removeAt(index) 16 | } 17 | return index to toMutableList 18 | } 19 | return index to toMutableList 20 | } 21 | 22 | internal fun containList( 23 | list1: List, 24 | collection: Collection, 25 | op: (String, T) -> Boolean 26 | ): Boolean { 27 | return indexOfList(list1, collection, op) > -1 28 | } 29 | 30 | internal fun indexOfList( 31 | list1: List, 32 | collection: Collection, 33 | op: (String, T) -> Boolean 34 | ): Int { 35 | var index = -1 36 | if (collection.size > list1.size) return index 37 | var iterator = collection.iterator() 38 | first@ for ((i, raw) in list1.withIndex()) { 39 | if (!iterator.hasNext()) break@first 40 | var next = iterator.next() 41 | if (!op(raw, next)) { 42 | index = -1 43 | iterator = collection.iterator() 44 | } else if (index == -1) index = i 45 | } 46 | return index 47 | } 48 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/utils/MessageTool.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.utils 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.block.Block 5 | import org.bukkit.entity.Entity 6 | import org.bukkit.entity.HumanEntity 7 | import org.bukkit.entity.Player 8 | import org.bukkit.inventory.ItemStack 9 | import top.iseason.bukkit.sakurabind.config.BaseSetting 10 | import top.iseason.bukkit.sakurabind.config.Config 11 | import top.iseason.bukkit.sakurabind.event.AutoBindMessageEvent 12 | import top.iseason.bukkit.sakurabind.event.PlayerDenyMessageEvent 13 | import top.iseason.bukkittemplate.hook.PlaceHolderHook 14 | import top.iseason.bukkittemplate.utils.bukkit.MessageUtils.sendColorMessage 15 | import top.iseason.bukkittemplate.utils.other.EasyCoolDown 16 | 17 | object MessageTool { 18 | /** 19 | * 玩家被禁止某种行为时发送消息,具有冷却机制 20 | */ 21 | fun denyMessageCoolDown( 22 | player: HumanEntity, 23 | message: String, 24 | setting: BaseSetting, 25 | item: ItemStack? = null, 26 | block: Block? = null, 27 | entity: Entity? = null, 28 | ) { 29 | val check = EasyCoolDown.check(player.uniqueId.toString() + message, Config.message_coolDown) 30 | val event = PlayerDenyMessageEvent(player, setting, message, check, item, block, entity) 31 | Bukkit.getPluginManager().callEvent(event) 32 | if (event.isCancelled) return 33 | if (!event.coolDown) 34 | player.sendColorMessage(event.message) 35 | } 36 | 37 | /** 38 | * 玩家物品被绑定时发送消息,具有冷却机制 39 | */ 40 | fun bindMessageCoolDown( 41 | player: HumanEntity, 42 | message: String, 43 | setting: BaseSetting, 44 | item: ItemStack, 45 | ) { 46 | val check = EasyCoolDown.check(player.uniqueId.toString() + message, Config.message_coolDown) 47 | val event = AutoBindMessageEvent(player, setting, message, check, item) 48 | Bukkit.getPluginManager().callEvent(event) 49 | if (event.isCancelled) return 50 | if (!event.coolDown) 51 | player.sendColorMessage(event.message) 52 | } 53 | 54 | fun messageCoolDown( 55 | player: HumanEntity, 56 | message: String, 57 | ) { 58 | if (!EasyCoolDown.check(player.uniqueId.toString() + message, Config.message_coolDown)) 59 | player.sendColorMessage(message) 60 | } 61 | 62 | fun sendNormal(player: Player, message: String) { 63 | player.sendColorMessage(PlaceHolderHook.setPlaceHolder(message, player)) 64 | } 65 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/utils/PlayerTool.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.utils 2 | 3 | 4 | import de.tr7zw.nbtapi.utils.MinecraftVersion 5 | import org.bukkit.entity.Player 6 | import org.bukkit.inventory.ItemStack 7 | 8 | object PlayerTool { 9 | fun getOffHandItem(player: Player): ItemStack? { 10 | return if (MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_9_R1)) player.inventory.itemInOffHand else null 11 | } 12 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/top/iseason/bukkit/sakurabind/utils/SendBackType.kt: -------------------------------------------------------------------------------- 1 | package top.iseason.bukkit.sakurabind.utils 2 | 3 | enum class SendBackType(var description: String) { 4 | API("API"), 5 | COMMON_CALLBACK("callback命令"), 6 | COMMON_SUPER_CALLBACK("supercallback命令"), 7 | BLOCK_FROM_TO("方块变化替换"), 8 | PLAYER_DROP("玩家丢弃"), 9 | CONTAINER_BREAK("容器破坏"), 10 | ITEM_DAMAGE("物品实体被毁坏"), 11 | ITEM_DE_SPAWN("物品实体自然消失"), 12 | PLAYER_DEATH("玩家死亡"), 13 | PLAYER_PICKUP("玩家捡起物品"), 14 | DROP("掉落物保护"), 15 | CONTAINER_DROP("容器掉落物中的物品保护"), 16 | SCANNER("扫描器"), 17 | } -------------------------------------------------------------------------------- /plugin/src/main/resources/placeholders.txt: -------------------------------------------------------------------------------- 1 | %sakurabind_has_lost% 判断玩家是否有遗失的物品,缓存3秒 返回 "true" 或 "false" 2 | 3 | %sakurabind_lost_count% 返回玩家暂存箱里的物品数量(一个格子为1) 4 | 5 | %sakurabind_hasbind_held% 判断玩家手持的物品是否绑定 绑定返回 "true" 无绑定返回 "false" 手上无物品返回 "null" 6 | 7 | %sakurabind_owner_uuid_held% 获取玩家手上物品的物主uuid 没有返回 "null" 8 | 9 | %sakurabind_owner_name_held% 获取玩家手上物品的物主名字 没有返回 "null" 10 | 11 | -------------------------------------------------------------------------------- /plugin/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ${name} 2 | main: ${main} 3 | version: ${version} 4 | authors: [ ${ author } ] 5 | api-version: 1.13 6 | depend: 7 | - NBTAPI 8 | softdepend: 9 | - PlaceholderAPI 10 | - AuthMe 11 | - MMOItems 12 | - ItemsAdder 13 | - Oraxen 14 | - GermPlugin 15 | - BanItem 16 | - mcMMO 17 | - GlobalMarketPlus 18 | - SweetMail 19 | - PlayerDataSQL 20 | - InvSync 21 | - HuskSync 22 | 23 | permission: 24 | sakurabind.bypass.all: 25 | default: op 26 | 27 | 28 | runtime-libraries: 29 | # If not defined, it will be 'libraries' 30 | # when start with @Plugin: will start with plugin data folder 31 | libraries-folder: 'libraries' 32 | repositories: 33 | - https://maven.aliyun.com/repository/public/ 34 | - https://repo.maven.apache.org/maven2/ 35 | parallel: true 36 | # ${kotlinVersion} 37 | libraries: 38 | - org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion} 39 | - org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion} 40 | - org.jetbrains.exposed:exposed-core:${exposedVersion} 41 | - org.jetbrains.exposed:exposed-dao:${exposedVersion} 42 | - org.jetbrains.exposed:exposed-jdbc:${exposedVersion} 43 | - org.jetbrains.exposed:exposed-java-time:${exposedVersion} 44 | - org.glassfish.jaxb:jaxb-runtime:2.3.8,1 45 | - org.ehcache:ehcache:3.10.8,1 46 | assembly: 47 | - org.apache.logging.log4j:log4j-api 48 | - org.slf4j:slf4j-api 49 | excludes: 50 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | val pluginName: String by settings 2 | rootProject.name = pluginName 3 | 4 | pluginManagement { 5 | //kotlin 版本 6 | val kotlinVersion: String by settings 7 | //shadowJar 版本 8 | val shadowJarVersion: String by settings 9 | plugins { 10 | kotlin("jvm") version kotlinVersion 11 | id("com.gradleup.shadow") version shadowJarVersion 12 | } 13 | } 14 | include("core", "plugin") 15 | --------------------------------------------------------------------------------