├── docs ├── .nojekyll ├── zh-cn │ ├── other-course.md │ ├── ecology.md │ ├── use-minecraft.md │ ├── dev-api.md │ └── dev-minecraft.md ├── _sidebar.md ├── README.md └── index.html ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── .gitignore ├── vcs.xml ├── misc.xml ├── gradle.xml └── uiDesigner.xml ├── minecraft ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── illtamer │ │ │ │ └── infinite │ │ │ │ └── bot │ │ │ │ └── minecraft │ │ │ │ ├── api │ │ │ │ ├── event │ │ │ │ │ ├── Listener.java │ │ │ │ │ ├── EventHandler.java │ │ │ │ │ └── EventPriority.java │ │ │ │ ├── adapter │ │ │ │ │ ├── CommandSender.java │ │ │ │ │ ├── Configuration.java │ │ │ │ │ ├── ConfigSection.java │ │ │ │ │ └── Bootstrap.java │ │ │ │ ├── IExternalExpansion.java │ │ │ │ ├── IExpansion.java │ │ │ │ ├── BotScheduler.java │ │ │ │ ├── StaticAPI.java │ │ │ │ └── EventExecutor.java │ │ │ │ ├── pojo │ │ │ │ ├── ExpansionIdentifier.java │ │ │ │ ├── PlayerData.java │ │ │ │ └── TimedBlockingCache.java │ │ │ │ ├── expansion │ │ │ │ ├── automation │ │ │ │ │ ├── annotation │ │ │ │ │ │ ├── ConfigClass.java │ │ │ │ │ │ └── ConfigField.java │ │ │ │ │ ├── factory │ │ │ │ │ │ └── SingletonFactory.java │ │ │ │ │ ├── Registration.java │ │ │ │ │ └── AutoLoadConfiguration.java │ │ │ │ ├── Language.java │ │ │ │ ├── manager │ │ │ │ │ ├── AbstractExternalExpansion.java │ │ │ │ │ ├── InfiniteExpansion.java │ │ │ │ │ ├── InfinitePluginLoader.java │ │ │ │ │ └── PluginClassLoader.java │ │ │ │ ├── ExpansionConfig.java │ │ │ │ ├── ExpansionLoader.java │ │ │ │ └── ExpansionLogger.java │ │ │ │ ├── exception │ │ │ │ ├── InitializationException.java │ │ │ │ └── InvalidExpansionException.java │ │ │ │ ├── util │ │ │ │ ├── Lambda.java │ │ │ │ ├── ThreadPoolUtil.java │ │ │ │ ├── ProcessBar.java │ │ │ │ ├── Optional.java │ │ │ │ ├── AutoConfigUtil.java │ │ │ │ ├── PluginUtil.java │ │ │ │ ├── ValidUtil.java │ │ │ │ ├── StringUtil.java │ │ │ │ └── ExpansionUtil.java │ │ │ │ ├── repository │ │ │ │ ├── PlayerDataRepository.java │ │ │ │ └── impl │ │ │ │ │ └── YamlPlayerDataRepository.java │ │ │ │ ├── start │ │ │ │ ├── origin │ │ │ │ │ ├── OriginBootstrap.java │ │ │ │ │ └── OriginConfig.java │ │ │ │ └── bukkit │ │ │ │ │ ├── BukkitBootstrap.java │ │ │ │ │ └── BukkitConfigSection.java │ │ │ │ ├── configuration │ │ │ │ ├── config │ │ │ │ │ ├── DataSourceConfiguration.java │ │ │ │ │ ├── ConfigFile.java │ │ │ │ │ ├── CommentConfiguration.java │ │ │ │ │ └── BotConfiguration.java │ │ │ │ ├── StatusCheckRunner.java │ │ │ │ ├── DependencyLoader.java │ │ │ │ └── BotNettyHolder.java │ │ │ │ └── listener │ │ │ │ ├── PluginListener.java │ │ │ │ ├── BungeeCommandListener.java │ │ │ │ ├── BukkitCommandListener.java │ │ │ │ └── handler │ │ │ │ └── CommandHelper.java │ │ └── resources │ │ │ ├── player_data.yml │ │ │ ├── plugin.yml │ │ │ └── config.yml │ └── test │ │ ├── java │ │ └── com │ │ │ └── illtamer │ │ │ └── infinite │ │ │ └── bot │ │ │ └── minecraft │ │ │ ├── Main.java │ │ │ └── BootstrapTests.java │ │ └── resources │ │ └── runtimeDownload.txt └── build.gradle ├── .gitignore ├── README.md ├── gradlew.bat └── gradlew /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/zh-cn/other-course.md: -------------------------------------------------------------------------------- 1 | # 群友提供 2 | 3 | 什么都没有哟 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'infinitebot4' 2 | include 'minecraft' 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IllTamer/infinitebot4/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/event/Listener.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.event; 2 | 3 | /** 4 | * 监听器接口 5 | * @see EventHandler 6 | * */ 7 | public interface Listener { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /minecraft/src/main/resources/player_data.yml: -------------------------------------------------------------------------------- 1 | # b67e1254-8373-494d-9b59-eb90c0df8e1d: 2 | # uuid: 8ca17c77-b187-3f34-a18d-7982005d4aab 3 | # valid_uuid: 57876712-e6a6-4cb2-ad64-19137f209beb 4 | # user_name: IllTamer # Player Name 5 | # user_id: 765743073 6 | # permission: [] -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/adapter/CommandSender.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.adapter; 2 | 3 | public interface CommandSender { 4 | 5 | boolean isOp(); 6 | 7 | void sendMessage(String message); 8 | 9 | void sendMessage(String[] messages); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /minecraft/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: InfiniteBot4 2 | main: com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap 3 | version: ${version} 4 | 5 | author: IllTamer 6 | 7 | load: POSTWORLD 8 | 9 | softdepend: 10 | - PlaceholderAPI 11 | - PlayerPoints 12 | - Vault 13 | 14 | commands: 15 | InfiniteBot4: 16 | aliases: 17 | - ib4 -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - 简介 2 | 3 | - [生态](/zh-cn/ecology.md) 4 | 5 | - 安装 6 | 7 | - [perpetua](https://iunlimit.github.io/perpetua/#/zh-cn/user/quick-start) 8 | - [minecraft](/zh-cn/use-minecraft.md) 9 | 10 | - 开发 11 | 12 | - [api](/zh-cn/dev-api.md) 13 | - [plugin-expansion](/zh-cn/dev-minecraft.md) 14 | 15 | - 其它资源 16 | 17 | - [群友提供](/zh-cn/other-course.md) -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/pojo/ExpansionIdentifier.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class ExpansionIdentifier { 9 | 10 | private String name; 11 | 12 | private String version; 13 | 14 | private String author; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/event/EventHandler.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.event; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 监听方法注解 7 | * */ 8 | @Documented 9 | @Target({ElementType.METHOD}) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface EventHandler { 12 | 13 | EventPriority priority() default EventPriority.DEFAULT; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/annotation/ConfigClass.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.automation.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 自动加载配置类类注解 7 | * */ 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | public @interface ConfigClass { 12 | 13 | /** 14 | * 配置类对应配置文件的名称 (含后缀) 15 | * */ 16 | String name() default ""; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/annotation/ConfigField.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.automation.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 自动加载配置类字段注解 7 | */ 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | public @interface ConfigField { 12 | 13 | /** 14 | * 字段坐标 15 | * */ 16 | String ref() default ""; 17 | 18 | /** 19 | * 缺省值 20 | * */ 21 | String value() default ""; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/event/EventPriority.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.event; 2 | 3 | /** 4 | * 事件优先级枚举 5 | * */ 6 | public enum EventPriority { 7 | 8 | /** 9 | * 最高 (最先触发) 10 | * */ 11 | HIGHEST, 12 | 13 | /** 14 | * 高 (较先触发) 15 | * */ 16 | HIGH, 17 | 18 | /** 19 | * 默认 (默认触发) 20 | * */ 21 | DEFAULT, 22 | 23 | /** 24 | * 较低 (较后触发) 25 | * */ 26 | LOW, 27 | 28 | /** 29 | * 最低 (最后触发) 30 | * */ 31 | LOWEST 32 | 33 | } 34 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/exception/InitializationException.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.exception; 2 | 3 | public class InitializationException extends RuntimeException { 4 | 5 | public InitializationException() { 6 | } 7 | 8 | public InitializationException(String message) { 9 | super(message); 10 | } 11 | 12 | public InitializationException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public InitializationException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/exception/InvalidExpansionException.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.exception; 2 | 3 | /** 4 | * 附属加载异常 5 | * */ 6 | public class InvalidExpansionException extends Exception { 7 | 8 | public InvalidExpansionException() {} 9 | 10 | public InvalidExpansionException(Throwable cause) { 11 | super(cause); 12 | } 13 | 14 | public InvalidExpansionException(String message) { 15 | super(message); 16 | } 17 | 18 | public InvalidExpansionException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /.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/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store 43 | 44 | ## custom 45 | .idea/ 46 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/Lambda.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.function.Function; 7 | 8 | @UtilityClass 9 | public class Lambda { 10 | 11 | /** 12 | * @param func 结果参数函数式调用 13 | * @param result 结果参数 14 | * */ 15 | @Nullable 16 | public static Return nullableInvoke(Function func, @Nullable Result result) { 17 | return result != null ? func.apply(result) : null; 18 | } 19 | 20 | /** 21 | * 三元运算 22 | * */ 23 | public static Type ternary(boolean condition, Type pass, Type deny) { 24 | return condition ? pass : deny; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /docs/zh-cn/ecology.md: -------------------------------------------------------------------------------- 1 | # 生态 2 | 3 | ## API 4 | 5 | [InfiniteBot3-API](https://github.com/IllTamer/infinitebot3/tree/main/api) 是本 sdk 的核心实现,其根据 go-cqhttp 文档标准实现了一套详尽的 消息构建 & 时间调度 系统。是其余 Java 下拓展生态的基础。 6 | 7 | ## for MineCraft 8 | 9 | [InfiniteBot3-MineCraft](https://github.com/IllTamer/infinitebot3/tree/main/minecraft) 是 InfiniteBot3 专为 Bukkit 服务器开发的一款高可用分布式可拓展机器人插件。其个性化的附属管理系统与高内聚,低耦合的设计理念为插件附属开发提供了强有力的支持,这也正是此插件深受开发者与服主喜爱的原因。 10 | 11 | ### expansion 12 | 13 | 基于 `InfiniteBot3-MineCraft` 的独立类加载系统,插件可加载遵循 [[附属开发规范]]() 的 嵌入式 / 外置 附属。由作者本人开发的嵌入式附属请见 [infinite-bot-3-expansion](https://github.com/IllTamer/infinite-bot-3-expansion) 14 | 15 | ## for Desktop 16 | 17 | [InfiniteBot3-Desktop](https://github.com/IllTamer/infinitebot3-desktop) 是基于 Electron 开发的拥有可视化界面的 InfiniteBot3 桌面控制中心,提供便捷的任务部署、监视与流控功能。 18 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/adapter/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.adapter; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.Map; 8 | 9 | public interface Configuration extends ConfigSection { 10 | 11 | @Nullable 12 | ConfigSection getSection(String path); 13 | 14 | ConfigSection createSection(String path, Map data); 15 | 16 | /** 17 | * 序列化 18 | * */ 19 | String saveToString(); 20 | 21 | /** 22 | * 将缓存数据保存到磁盘 23 | * */ 24 | void save(File file) throws IOException; 25 | 26 | /** 27 | * 将磁盘文件加载到缓存 28 | * */ 29 | void load(File file) throws IOException; 30 | 31 | /** 32 | * 从字符串加载 33 | * */ 34 | void load(String yaml); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /minecraft/src/test/java/com/illtamer/infinite/bot/minecraft/Main.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft; 2 | 3 | import java.util.Scanner; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | Scanner scanner = new Scanner(System.in); 9 | int n = scanner.nextInt(); 10 | 11 | int[] cells = new int[n]; 12 | for (int i = 0; i < n; i++) { 13 | cells[i] = scanner.nextInt(); 14 | } 15 | 16 | int minDrops = 0; 17 | for (int i = 0; i < n; i++) { 18 | if (cells[i] >= 6) { 19 | int extraDrops = cells[i] - 5; 20 | minDrops += extraDrops; 21 | 22 | if (i > 0) cells[i - 1] += extraDrops; 23 | if (i < n - 1) cells[i + 1] += extraDrops; 24 | cells[i] = 5; 25 | } 26 | } 27 | System.out.println(minDrops); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/adapter/ConfigSection.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.adapter; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | public interface ConfigSection { 8 | 9 | String getString(String path); 10 | 11 | String getString(String path, String def); 12 | 13 | List getStringList(String path); 14 | 15 | Integer getInt(String path); 16 | 17 | Integer getInt(String path, Integer def); 18 | 19 | Long getLong(String path); 20 | 21 | Long getLong(String path, Long def); 22 | 23 | Boolean getBoolean(String path); 24 | 25 | Boolean getBoolean(String path, Boolean def); 26 | 27 | List getLongList(String path); 28 | 29 | void set(String path, Object value); 30 | 31 | Set getKeys(boolean deep); 32 | 33 | Map getValues(boolean deep); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Infinite Bot v4 2 | 3 | ``` 4 | .___ _____.__ .__ __ __________ __ _____ 5 | | | _____/ ____\__| ____ |__|/ |_ ____\______ \ _____/ |_ / | | 6 | | |/ \ __\| |/ \| \ __\/ __ \| | _// _ \ __\ / | |_ 7 | | | | \ | | | | \ || | \ ___/| | ( <_> ) | / ^ / 8 | |___|___| /__| |__|___| /__||__| \___ >______ /\____/|__| \____ | 9 | \/ \/ \/ \/ |__| 10 | ``` 11 | 12 | ## 简介 13 | 14 | 第四代 Infinite QQ 机器人。基于 perpetua-sdk-for-java,为 JavaSE 与 Bukkit 环境下 QQ 机器人的开发提供附属注册、配置管理、事件分发、等功能支持。 15 | 16 | ## 声明 17 | 18 | - 若您在使用时有任何疑问,欢迎入群讨论咨询 `QQ: 863522624` 19 | 20 | - 若您为 Minecraft 公益服主且服务器资源难以承受 perpetua 的运行,欢迎 [[联系我]](https://api.vvhan.com/api/qqCard?qq=765743073) 。我与我的云服务很乐意为您提供一份力所能及的援助。 21 | 22 | ## 致谢 23 | 24 | - 感谢 小豆子、阿丽塔、polar、一口小雨、黑土、仔仔 等腐竹在测试、策划方面提供的帮助与支持 25 | 26 | - 感谢机器人插件的先驱者 [@Albert](https://github.com/mcdoeswhat) -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 |
Loading ...
13 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/adapter/Bootstrap.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api.adapter; 2 | 3 | import java.io.File; 4 | import java.io.InputStream; 5 | import java.util.logging.Logger; 6 | 7 | public interface Bootstrap { 8 | 9 | /** 10 | * 保存文件资源到磁盘 11 | * @param replace 是否替换 12 | * */ 13 | void saveResource(String fileName, boolean replace); 14 | 15 | /** 16 | * 创建配置类实例 17 | * */ 18 | Configuration createConfig(); 19 | 20 | /** 21 | * 获取资源文件夹 22 | * */ 23 | File getDataFolder(); 24 | 25 | /** 26 | * 获取日志实例 27 | * */ 28 | Logger getLogger(); 29 | 30 | /** 31 | * 获取 jar 内资源文件输入流 32 | * @param fileName 文件路径 33 | * */ 34 | InputStream getResource(String fileName); 35 | 36 | /** 37 | * 获取当前启动器类加载器 38 | * */ 39 | ClassLoader getInstClassLoader(); 40 | 41 | Type getType(); 42 | 43 | enum Type { 44 | // bukkit server 45 | BUKKIT, 46 | // bungee server 47 | BUNGEE, 48 | // j2se main 49 | ORIGIN 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/repository/PlayerDataRepository.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.repository; 2 | 3 | import com.illtamer.infinite.bot.minecraft.pojo.PlayerData; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.UUID; 8 | 9 | public interface PlayerDataRepository { 10 | 11 | boolean save(@NotNull PlayerData data); 12 | 13 | @Nullable 14 | PlayerData queryByUUID(@NotNull UUID uuid); 15 | 16 | @Nullable 17 | PlayerData queryByUUID(@NotNull String uuid); 18 | 19 | @Nullable 20 | PlayerData queryByUserId(@NotNull Long userId); 21 | 22 | /** 23 | * 更新玩家数据 24 | * @return 更新前的玩家数据 25 | * */ 26 | @Nullable 27 | PlayerData update(@NotNull PlayerData data); 28 | 29 | /** 30 | * 删除玩家数据 31 | * @return 移除前的玩家数据 32 | * */ 33 | @Nullable 34 | PlayerData delete(@NotNull String uuid); 35 | 36 | /** 37 | * 删除玩家数据 38 | * @return 移除前的玩家数据 39 | * */ 40 | @Nullable 41 | PlayerData delete(@NotNull Long userId); 42 | 43 | /** 44 | * 将缓存数据写入硬盘 45 | * */ 46 | void saveCacheData(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/IExternalExpansion.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api; 2 | 3 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 4 | import org.bukkit.plugin.Plugin; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * Infinite Bot 外部扩展附属接口 9 | * */ 10 | public interface IExternalExpansion extends IExpansion { 11 | 12 | /** 13 | * 主动注册附属 TODO 测试位于附属目录下能否正常使用,能否检测不正确加载 14 | * @apiNote 主动注册时,附属不可位于附属目录下,否则将被重复注册抛出异常 15 | * */ 16 | void register(@NotNull Plugin plugin); 17 | 18 | /** 19 | * 主动卸载附属 20 | * */ 21 | void unregister(); 22 | 23 | /** 24 | * 附属是否已被注册 25 | * */ 26 | boolean isRegister(); 27 | 28 | /** 29 | * 是否允许注册 30 | * */ 31 | default boolean canRegister() { 32 | return true; 33 | } 34 | 35 | /** 36 | * 是否为持久化实例 37 | *

38 | * 当此方法返回为 true 时,指令将不会卸载实例(其余途径如注册插件自身被卸载、IB3被卸载等仍会触发卸载) 39 | * */ 40 | default boolean isPersist() { 41 | return true; 42 | } 43 | 44 | /** 45 | * 获取插件本体实例 46 | * */ 47 | default BukkitBootstrap getPluginInstance() { 48 | return BukkitBootstrap.getInstance(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/origin/OriginBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.start.origin; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration; 5 | 6 | import java.io.File; 7 | import java.io.InputStream; 8 | import java.util.logging.Logger; 9 | 10 | // TODO 11 | public class OriginBootstrap implements Bootstrap { 12 | 13 | @Override 14 | public void saveResource(String fileName, boolean replace) { 15 | 16 | } 17 | 18 | @Override 19 | public Configuration createConfig() { 20 | return null; 21 | } 22 | 23 | @Override 24 | public File getDataFolder() { 25 | return null; 26 | } 27 | 28 | @Override 29 | public Logger getLogger() { 30 | return null; 31 | } 32 | 33 | @Override 34 | public InputStream getResource(String fileName) { 35 | return null; 36 | } 37 | 38 | @Override 39 | public ClassLoader getInstClassLoader() { 40 | return null; 41 | } 42 | 43 | @Override 44 | public Type getType() { 45 | return Type.ORIGIN; 46 | } 47 | 48 | public static void main(String[] args) { 49 | // TODO 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ThreadPoolUtil.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | public final class ThreadPoolUtil { 4 | 5 | private ThreadPoolUtil() {} 6 | 7 | /** 8 | * Each tasks blocks 90% of the time, and works only 10% of its 9 | * lifetime. That is, I/O intensive pool 10 | * @return io intensive Thread pool size 11 | */ 12 | public static int ioIntensivePoolSize() { 13 | double blockingCoefficient = 0.8; 14 | return poolSize(blockingCoefficient); 15 | } 16 | 17 | /** 18 | * 19 | * Number of threads = Number of Available Cores / (1 - Blocking 20 | * Coefficient) where the blocking coefficient is between 0 and 1. 21 | * 22 | * A computation-intensive task has a blocking coefficient of 0, whereas an 23 | * IO-intensive task has a value close to 1, 24 | * so we don't have to worry about the value reaching 1. 25 | * @param blockingCoefficient the coefficient 26 | * @return Thread pool size 27 | */ 28 | public static int poolSize(double blockingCoefficient) { 29 | int numberOfCores = Runtime.getRuntime().availableProcessors(); 30 | return (int) (numberOfCores / (1 - blockingCoefficient)); 31 | } 32 | } -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ProcessBar.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 5 | 6 | public class ProcessBar { 7 | 8 | private final String endStr; 9 | 10 | private int current; 11 | private final int total; 12 | 13 | private ProcessBar(int total) { 14 | this.total = total; 15 | this.current = 0; 16 | this.endStr = StaticAPI.getInstance().getType() == Bootstrap.Type.BUKKIT ? "]\n" : "]"; 17 | } 18 | 19 | /** 20 | * 增加进度 21 | * */ 22 | public synchronized void count() { 23 | current += 1; 24 | // print, check finish 25 | print(); 26 | } 27 | 28 | private void print() { 29 | System.out.printf("\r%d/%d [", current, total); 30 | for (int j = 0; j < current; ++ j) { 31 | System.out.print("="); 32 | } 33 | for (int j = current; j < total; ++ j) { 34 | System.out.print(" "); 35 | } 36 | System.out.print(endStr); 37 | if (current == total) { 38 | System.out.println(); 39 | } 40 | } 41 | 42 | public static ProcessBar create(int total) { 43 | return new ProcessBar(total); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/IExpansion.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.slf4j.Logger; 5 | 6 | import java.io.File; 7 | import java.io.InputStream; 8 | 9 | /** 10 | * Infinite Bot 附属接口 11 | * */ 12 | public interface IExpansion { 13 | 14 | /** 15 | * 附属启用时调用 16 | * */ 17 | void onEnable(); 18 | 19 | /** 20 | * 附属卸载时调用 21 | * */ 22 | void onDisable(); 23 | 24 | /** 25 | * 附属是否被加载 26 | * */ 27 | boolean isEnabled(); 28 | 29 | /** 30 | * 获取附属名称 31 | * @apiNote 名称为任意不包含 '-' 的一串字符 32 | * */ 33 | String getExpansionName(); 34 | 35 | /** 36 | * 获取附属版本 37 | * @apiNote 语义化版本 38 | * */ 39 | String getVersion(); 40 | 41 | /** 42 | * 获取附属作者 43 | * @apiNote 任意字符 44 | * */ 45 | String getAuthor(); 46 | 47 | /** 48 | * 获取当前附属logger 49 | * */ 50 | Logger getLogger(); 51 | 52 | /** 53 | * 获取当前附属配置文件夹 54 | * */ 55 | File getDataFolder(); 56 | 57 | /** 58 | * 获取附属资源文件 59 | * */ 60 | InputStream getResource(String name); 61 | 62 | /** 63 | * 储存附属jar中的资源文件到附属文件夹 64 | * */ 65 | void saveResource(String path, boolean replace); 66 | 67 | @Override 68 | @NotNull 69 | String toString(); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/config/DataSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration.config; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | 6 | import javax.sql.DataSource; 7 | import java.util.Map; 8 | 9 | public class DataSourceConfiguration { 10 | 11 | private final HikariDataSource dataSource; 12 | 13 | public DataSourceConfiguration() { 14 | final Map mysqlConfig = BotConfiguration.database.mysqlConfig; 15 | 16 | HikariConfig config = new HikariConfig(); 17 | config.setJdbcUrl("jdbc:mysql://" + mysqlConfig.get("host") + ":" + mysqlConfig.get("port") + "/" + mysqlConfig.get("database")); 18 | config.setUsername((String) mysqlConfig.get("username")); 19 | config.setPassword((String) mysqlConfig.get("password")); 20 | config.setMaximumPoolSize(20); // 根据需要调整,建议 10~30 21 | config.setMinimumIdle(5); 22 | config.setConnectionTimeout(30_000); 23 | config.setIdleTimeout(600_000); 24 | config.setMaxLifetime(1800_000); 25 | config.setLeakDetectionThreshold(60_000); // 60秒未归还视为泄漏(开发环境可用) 26 | config.setAutoCommit(true); 27 | this.dataSource = new HikariDataSource(config); 28 | } 29 | 30 | public DataSource getDataSource() { 31 | return dataSource; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/PluginListener.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.listener; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion; 4 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader; 5 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.EventPriority; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.event.server.PluginDisableEvent; 10 | import org.bukkit.plugin.Plugin; 11 | 12 | import java.util.logging.Logger; 13 | 14 | public class PluginListener implements Listener { 15 | 16 | private final ExpansionLoader expansionLoader; 17 | private final Logger logger; 18 | 19 | public PluginListener(BukkitBootstrap instance) { 20 | this.expansionLoader = instance.getExpansionLoader(); 21 | this.logger = instance.getLogger(); 22 | } 23 | 24 | @EventHandler(priority = EventPriority.MONITOR) 25 | public void onDisabled(PluginDisableEvent event) { 26 | final Plugin plugin = event.getPlugin(); 27 | final IExternalExpansion externalExpansion = expansionLoader.getExternalExpansion(plugin); 28 | if (externalExpansion != null) { 29 | logger.warning("插件 " + plugin.getName() + " 在卸载时未主动注销附属,被动注销中..."); 30 | expansionLoader.disableExternalExpansion(externalExpansion); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/Optional.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import com.illtamer.perpetua.sdk.util.Assert; 4 | 5 | import java.util.function.Consumer; 6 | import java.util.function.Supplier; 7 | 8 | public final class Optional { 9 | 10 | private T value; 11 | 12 | private Optional() {} 13 | 14 | private Optional(T value) { 15 | this.value = value; 16 | } 17 | 18 | public void ifPresent(Consumer consumer) { 19 | if (value != null) 20 | consumer.accept(value); 21 | } 22 | 23 | public T orElseThrow(Supplier exceptionSupplier) throws X { 24 | if (value != null) { 25 | return value; 26 | } else { 27 | throw exceptionSupplier.get(); 28 | } 29 | } 30 | 31 | public void set(T value) { 32 | Assert.isNull(this.value, "Optional value exists!"); 33 | this.value = value; 34 | } 35 | 36 | public T get() { 37 | Assert.notNull(value, "Unexpect null value."); 38 | return value; 39 | } 40 | 41 | public static Optional empty() { 42 | return new Optional<>(); 43 | } 44 | 45 | public static Optional of(T value) { 46 | return new Optional<>(value); 47 | } 48 | 49 | public static Optional ofNullable(T value) { 50 | return value == null ? empty() : of(value); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/AutoConfigUtil.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | @UtilityClass 11 | public class AutoConfigUtil { 12 | 13 | private static final Map, Function> DEFAULT_CAST_MAP = new HashMap<>(); 14 | 15 | static { 16 | DEFAULT_CAST_MAP.put(String.class, s -> s); 17 | DEFAULT_CAST_MAP.put(Integer.class, Integer::parseInt); 18 | DEFAULT_CAST_MAP.put(Long.class, Long::parseLong); 19 | DEFAULT_CAST_MAP.put(Float.class, Float::parseFloat); 20 | DEFAULT_CAST_MAP.put(Double.class, Double::parseDouble); 21 | DEFAULT_CAST_MAP.put(Byte.class, Byte::parseByte); 22 | DEFAULT_CAST_MAP.put(Short.class, Short::parseShort); 23 | } 24 | 25 | @Nullable 26 | public static Object castDefaultBasicType(String defaultValue, Class fieldType) { 27 | for (Map.Entry, Function> entry : DEFAULT_CAST_MAP.entrySet()) { 28 | if (entry.getKey().equals(fieldType)) { 29 | try { 30 | return entry.getValue().apply(defaultValue); 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | break; 35 | } 36 | } 37 | return null; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /minecraft/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # .___ _____.__ .__ __ __________ __ ________ 2 | # | | _____/ ____\__| ____ |__|/ |_ ____\______ \ _____/ |_ \_____ \ 3 | # | |/ \ __\| |/ \| \ __\/ __ \| | _// _ \ __\ _(__ < 4 | # | | | \ | | | | \ || | \ ___/| | ( <_> ) | / \ 5 | # |___|___| /__| |__|___| /__||__| \___ >______ /\____/|__| /______ / 6 | # \/ \/ \/ \/ \/ 7 | # 8 | # - by IllTamer 9 | # GitHub: 10 | # - 前置: https://github.com/IUnlimit/perpetua 11 | # - 本插件: https://github.com/IllTamer/infinitebot4 12 | 13 | # 插件配置部分 14 | # 该配置可重载 15 | main: 16 | # 管理员账号列表 17 | admins: 18 | - 765743073 19 | # 监听的群列表 20 | # bungee: true 时此配置项无效 21 | groups: 22 | - 863522624 23 | 24 | # 数据储存方式 25 | # 该配置仅在加载插件时读取 26 | database: 27 | # 可选择的储存方式 28 | # 'yaml' - 本地yaml储存,数据仅插件加载服可用 29 | # 'mysql' - mysql数据库储存,群组服数据互通必选 30 | type: 'yaml' 31 | config: 32 | mysql: 33 | host: 'localhost' 34 | port: 3306 35 | username: 'root' 36 | password: 'root' 37 | database: 'minecraft' 38 | 39 | # 连接配置项 40 | # 该配置可重载 41 | connection: 42 | # 当前客户端连接名称 43 | # 可为空,为空时当前连接不配置名称 44 | # 当附属使用分布式 API 时,建议配置,配置时应确保该名称全局唯一 45 | name: 'ib-1' 46 | # perpetua webapi 配置 47 | webapi: 48 | # 进程所在域名 49 | host: '127.0.0.1' 50 | # 服务开放端口 51 | port: 8080 52 | # 通信验证 token 53 | # 若您在 OneBot 实现中配置了此项,请填写,否则请留空 54 | authorization: '' -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/StatusCheckRunner.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI; 4 | import com.illtamer.perpetua.sdk.entity.transfer.entity.LoginInfo; 5 | import com.illtamer.perpetua.sdk.entity.transfer.entity.Status; 6 | import com.illtamer.perpetua.sdk.handler.OpenAPIHandling; 7 | import com.illtamer.perpetua.sdk.websocket.OneBotConnection; 8 | import lombok.Getter; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.logging.Logger; 12 | 13 | /** 14 | * 机器人状态检查 Runnable 15 | * */ 16 | public class StatusCheckRunner implements Runnable { 17 | 18 | @Getter 19 | private static long lastRefreshTime; 20 | @Getter 21 | @Nullable 22 | private static LoginInfo loginInfo; 23 | 24 | private final Logger log; 25 | 26 | public StatusCheckRunner(Logger logger) { 27 | this.log = logger; 28 | } 29 | 30 | @Override 31 | public void run() { 32 | if (!OneBotConnection.isRunning()) { 33 | log.warning("检测到 WebSocket 连接断开,尝试重连 perpetua 中"); 34 | loginInfo = null; 35 | } else { 36 | try { 37 | loginInfo = OpenAPIHandling.getLoginInfo(); 38 | } catch (Exception ignore) { 39 | log.warning("获取账号信息失败,尝试重连 perpetua 中"); 40 | loginInfo = null; 41 | } 42 | } 43 | if (loginInfo == null) { 44 | StaticAPI.reconnected(); 45 | } 46 | lastRefreshTime = System.currentTimeMillis(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/BungeeCommandListener.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.listener; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.listener.handler.CommandHelper; 5 | import net.md_5.bungee.api.CommandSender; 6 | import net.md_5.bungee.api.chat.TextComponent; 7 | import net.md_5.bungee.api.plugin.Command; 8 | 9 | import java.util.Arrays; 10 | 11 | public class BungeeCommandListener extends Command { 12 | 13 | public BungeeCommandListener() { 14 | super("InfiniteBot3", null, "ib3"); 15 | } 16 | 17 | @Override 18 | public void execute(CommandSender sender, String[] args) { 19 | CommandHelper.onCommand(new com.illtamer.infinite.bot.minecraft.api.adapter.CommandSender() { 20 | @Override 21 | public boolean isOp() { 22 | return sender.hasPermission("ib3-op-test-permission"); 23 | } 24 | 25 | @Override 26 | public void sendMessage(String message) { 27 | TextComponent comp = new TextComponent(); 28 | comp.setText(message); 29 | sender.sendMessage(comp); 30 | } 31 | 32 | @Override 33 | public void sendMessage(String[] messages) { 34 | TextComponent[] components = Arrays.stream(messages).map(message -> { 35 | TextComponent comp = new TextComponent(); 36 | comp.setText(message); 37 | return comp; 38 | }).toArray(TextComponent[]::new); 39 | sender.sendMessage(components); 40 | } 41 | }, args, Bootstrap.Type.BUNGEE); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /docs/zh-cn/use-minecraft.md: -------------------------------------------------------------------------------- 1 | # for Minecraft 2 | 3 | 基于 go-cqhttp 提供的 API 服务,IB3 天然支持群组服/分布式服务。 4 | 5 | 您需要做的仅仅是在 BC 端上加载插件,与需要互通的其它子端配置相同地连接参数与 mysql 服务。 6 | 接着,您可以在不同的子端上加载不同的附属:如登陆服加载 defence-manager 开启白名单验证码服务,在生存服加载 chat-manager 运行消息互通服务... 7 | 8 | (好叭,其实 BC 端正在适配中) 9 | 10 | ## 环境 11 | 12 | ### Java 8 13 | 14 | 该版本可直接运行运行插件 15 | 16 | ### Java 9+ 17 | 18 | 若您的服务器使用了 Java9 及以上的版本,则需在启动的批处理文件中加入以下 JVM 参数允许来自未命名模块的反射调用。 19 | 20 | ```cmd 21 | --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED 22 | ``` 23 | 24 | ## 部署 25 | 26 | - 插件 27 | 28 | > **Notice**: 插件本体理论支持 热加载&热卸载,但不建议 **频繁的热部署插件**。插件附属管理为插件内部支持实现,其完全支持热部署,可放心食用。 29 | 30 | 请您将构建的 `InfiniteBot-minecraft-all-3.0.0-SNAPSHOT.jar` 放入服务器的 `plugins` 文件夹内。 31 | 待生成配置文件后,请参考 [[config.yml]](src/main/resources/config.yml) 内注释详细配置 go-cqhttp 的连接信息。 32 | 33 | - 插件附属 34 | 35 | 遵守[附属开发规范](docs/Expansion.md)的插件附属应当被放置在 `plugins\InfiniteBot3\expansions` 路径内。若附属注册了相应的配置文件,则在附属被加载后与附属注册名称相同的配置文件夹也将在同级目录下生成。 36 | 37 | ## 指令 38 | 39 | 所有的指令与补全仅管理员(`OP`)可用 40 | 41 | ```text 42 | ib3: 43 | ├──help: 获取帮助 44 | ├──check: 检查账号的连接状态 45 | ├──reload: 重载配置文件 46 | ├──expansions 47 | │ ├──list: 列出所有加载的附属名称 48 | │ └──reload: 重载 expansions 目录下所有附属 49 | ├──load 50 | │ └──[附属文件名]: 加载名称对应附属 51 | └──unload 52 | └──[附属名称]: 卸载名称对应附属 53 | ``` 54 | 55 | ## 资源文件夹结构 56 | 57 | ```text 58 | InfiniteBot3 59 | ├──expansions: 附属及其配置文件夹 60 | │ ├──basic-manager-1.0.jar: 基础管理插件 61 | │ ├──BasicManager: 基础管理插件配置文件夹 62 | │ │ └──config.yml: 基础管理插件配置文件 63 | │ └──... 64 | ├──config.yml: 插件本体配置文件 65 | └──player_data.yml: 66 | ``` 67 | 68 | ## 附属相关 69 | 70 | ### 下载 71 | 72 | 详见仓库 [infinite-bot-3-expansion](https://github.com/IllTamer/infinite-bot-3-expansion) 73 | 74 | 75 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/config/ConfigFile.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration.config; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | public class ConfigFile { 10 | private final String name; 11 | private final Bootstrap instance; 12 | private File file; 13 | private volatile Configuration config; 14 | 15 | public ConfigFile(String name, Bootstrap instance) { 16 | this.name = name; 17 | this.instance = instance; 18 | this.config = this.load(); 19 | } 20 | 21 | public void save() { 22 | try { 23 | this.config.save(this.file); 24 | } catch (IOException var2) { 25 | var2.printStackTrace(); 26 | } 27 | this.config = this.load(); 28 | } 29 | 30 | /** 31 | * 更新并保存配置文件 32 | * */ 33 | public void update(String yaml) { 34 | config.load(yaml); 35 | save(); 36 | } 37 | 38 | private Configuration load() { 39 | File file = new File(this.instance.getDataFolder(), this.name); 40 | if (!file.exists()) { 41 | this.instance.saveResource(this.name, false); 42 | } 43 | 44 | Configuration configuration = instance.createConfig(); 45 | try { 46 | configuration.load(file); 47 | this.file = file; 48 | } catch (IOException var4) { 49 | var4.printStackTrace(); 50 | } 51 | return configuration; 52 | } 53 | 54 | public void reload() { 55 | this.config = this.load(); 56 | } 57 | 58 | public Configuration getConfig() { 59 | return this.config; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/BukkitCommandListener.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.listener; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.listener.handler.CommandHelper; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.command.TabExecutor; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.List; 12 | 13 | public class BukkitCommandListener implements TabExecutor { 14 | 15 | @Override 16 | public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { 17 | return CommandHelper.onCommand(new BukkitCommandSender(sender), args, Bootstrap.Type.BUKKIT); 18 | } 19 | 20 | @Nullable 21 | @Override 22 | public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { 23 | return CommandHelper.onTabComplete(new BukkitCommandSender(sender), args); 24 | } 25 | 26 | private static class BukkitCommandSender implements com.illtamer.infinite.bot.minecraft.api.adapter.CommandSender { 27 | 28 | private final CommandSender sender; 29 | 30 | private BukkitCommandSender(CommandSender sender) { 31 | this.sender = sender; 32 | } 33 | 34 | @Override 35 | public boolean isOp() { 36 | return sender.isOp(); 37 | } 38 | 39 | @Override 40 | public void sendMessage(String message) { 41 | sender.sendMessage(message); 42 | } 43 | 44 | @Override 45 | public void sendMessage(String[] messages) { 46 | sender.sendMessage(messages); 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/pojo/PlayerData.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.pojo; 2 | 3 | import lombok.Data; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.OfflinePlayer; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.UUID; 12 | 13 | @Data 14 | public class PlayerData { 15 | 16 | /** 17 | * Use for database 18 | * */ 19 | private Integer id; 20 | 21 | /** 22 | * Minecraft UUID 23 | * */ 24 | private String uuid; 25 | 26 | /** 27 | * Valid UUID by MoJang 28 | * */ 29 | private String validUUID; 30 | 31 | /** 32 | * qq 号 33 | * */ 34 | private Long userId; 35 | 36 | private List permission; 37 | 38 | /** 39 | * 获取倾向的首个不为空的 uuid 40 | *

41 | * TODO 倾向可配置 42 | * */ 43 | @Nullable 44 | public String getPreferUUID() { 45 | return uuid == null ? validUUID : uuid; 46 | } 47 | 48 | @Nullable 49 | public OfflinePlayer getValidOfflinePlayer() { 50 | return validUUID != null ? Bukkit.getOfflinePlayer(UUID.fromString(validUUID)) : null; 51 | } 52 | 53 | @Nullable 54 | public OfflinePlayer getOfflinePlayer() { 55 | return uuid != null ? Bukkit.getOfflinePlayer(UUID.fromString(uuid)) : null; 56 | } 57 | 58 | /** 59 | * Use for yaml 60 | * */ 61 | public Map toMap() { 62 | LinkedHashMap map = new LinkedHashMap<>(3); 63 | if (uuid != null) 64 | map.put("uuid", uuid); 65 | if (validUUID != null) 66 | map.put("user_name", validUUID); 67 | if (userId != null) 68 | map.put("user_id", userId); 69 | if (permission != null && permission.size() != 0) 70 | map.put("permission", permission); 71 | return map; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/PluginUtil.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 4 | import lombok.experimental.UtilityClass; 5 | 6 | @UtilityClass 7 | public class PluginUtil { 8 | 9 | public static String parseColor(String s) { 10 | return s.replace('&', '§').replace("§§", "&"); 11 | } 12 | 13 | public static String clearColor(String s) { 14 | char[] chars = s.toCharArray(); 15 | StringBuilder builder = new StringBuilder(); 16 | 17 | for(int i = 0; i < chars.length; ++i) { 18 | if (chars[i] == 167 && i + 1 <= chars.length) { 19 | char next = chars[i + 1]; 20 | if (checkColor(next)) { 21 | ++i; 22 | continue; 23 | } 24 | } 25 | builder.append(chars[i]); 26 | } 27 | return builder.toString(); 28 | } 29 | 30 | private static boolean checkColor(char next) { 31 | if (next >= '0' && next <= '9') return true; 32 | if ((next >= 'a' && next <= 'f') || (next >= 'A' && next <= 'F')) return true; 33 | if ((next >= 'k' && next <= 'o') || (next >= 'K' && next <= 'O')) return true; 34 | return next == 'r' || next == 'x' || next == 'R' || next == 'X'; 35 | } 36 | 37 | public static class Version { 38 | 39 | public static final String VERSION; 40 | 41 | static { 42 | final String packageName = BukkitBootstrap.getInstance().getServer().getClass().getPackage().getName(); 43 | VERSION = packageName.substring(packageName.lastIndexOf('.') + 1); 44 | } 45 | 46 | /** 47 | * @param number 中版本号,例如 v1_12_R1 -> 12 48 | * */ 49 | public static boolean upper(int number) { 50 | final String[] split = VERSION.split("_"); 51 | return Integer.parseInt(split[1]) > number; 52 | } 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/BotScheduler.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import com.illtamer.infinite.bot.minecraft.util.ThreadPoolUtil; 5 | 6 | import java.util.concurrent.*; 7 | 8 | public class BotScheduler { 9 | 10 | public static final ExecutorService IO_INTENSIVE; 11 | public static final ScheduledExecutorService TIMER; 12 | 13 | public static void runTask(Runnable runnable) { 14 | IO_INTENSIVE.submit(runnable); 15 | } 16 | 17 | public static void runTask(Callable callable) { 18 | IO_INTENSIVE.submit(callable); 19 | } 20 | 21 | public static Future runTask(Runnable runnable, T t) { 22 | return IO_INTENSIVE.submit(runnable, t); 23 | } 24 | 25 | public static ScheduledFuture runTaskLater(Runnable runnable, long delaySeconds) { 26 | return TIMER.schedule(runnable, delaySeconds, TimeUnit.SECONDS); 27 | } 28 | 29 | public static ScheduledFuture runTaskTimer(Runnable runnable, long initDelay, long periodSeconds) { 30 | return TIMER.scheduleAtFixedRate(runnable, initDelay, periodSeconds, TimeUnit.SECONDS); 31 | } 32 | 33 | public static void close() { 34 | IO_INTENSIVE.shutdownNow(); 35 | TIMER.shutdownNow(); 36 | } 37 | 38 | static { 39 | TIMER = new ScheduledThreadPoolExecutor(1, 40 | new ThreadFactoryBuilder() 41 | .setNameFormat("bot-schedule-%d") 42 | .setUncaughtExceptionHandler((t, e) -> e.printStackTrace()) 43 | .build()); 44 | IO_INTENSIVE = new ThreadPoolExecutor( 45 | 3, 46 | ThreadPoolUtil.poolSize(0.90), 47 | 60L, TimeUnit.SECONDS, 48 | new SynchronousQueue<>(), 49 | new ThreadFactoryBuilder() 50 | .setNameFormat("bot-thread-%d") 51 | .setUncaughtExceptionHandler((t, e) -> e.printStackTrace()) 52 | .build()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/Language.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.perpetua.sdk.util.Assert; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * 语言消息配置类 11 | * */ 12 | public class Language { 13 | 14 | private final ExpansionConfig lanConfig; 15 | 16 | protected Language(String fileName, int version, IExpansion expansion) { 17 | this.lanConfig = new ExpansionConfig(fileName + ".yml", expansion, version); 18 | } 19 | 20 | /** 21 | * 获取语言文本 22 | * @param nodes 配置节点 23 | * */ 24 | @NotNull 25 | public String get(String... nodes) { 26 | Assert.notEmpty(nodes, "Language nodes can not be empty!"); 27 | StringBuilder builder = new StringBuilder(); 28 | for (String node : nodes) { 29 | Assert.notEmpty(node, "Language node can not be null!"); 30 | builder.append('.').append(node); 31 | } 32 | builder.deleteCharAt(0); 33 | return Optional.ofNullable(lanConfig.getConfig().getString(builder.toString())).orElse(""); 34 | } 35 | 36 | /** 37 | * 重新载入语言文件 38 | * */ 39 | public void reload() { 40 | lanConfig.reload(); 41 | } 42 | 43 | public static Language of(IExpansion expansion) { 44 | return of("language", expansion); 45 | } 46 | 47 | /** 48 | * @param name 语言文件前缀名 language 49 | * */ 50 | public static Language of(String name, IExpansion expansion) { 51 | return of(name, 0, "zh_CN", expansion); 52 | } 53 | 54 | /** 55 | * @param name 语言文件前缀名 language 56 | * */ 57 | public static Language of(String name, int version, IExpansion expansion) { 58 | return of(name, version, "zh_CN", expansion); 59 | } 60 | 61 | 62 | /** 63 | * @param name 语言文件前缀名 language 64 | * @param type 语言类型后缀 zh_CN 65 | * */ 66 | public static Language of(String name, int version, String type, IExpansion expansion) { 67 | return new Language(name + '-' + type, version, expansion); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Infinite Bot v4 2 | 3 | ``` 4 | .___ _____.__ .__ __ __________ __ _____ 5 | | | _____/ ____\__| ____ |__|/ |_ ____\______ \ _____/ |_ / | | 6 | | |/ \ __\| |/ \| \ __\/ __ \| | _// _ \ __\ / | |_ 7 | | | | \ | | | | \ || | \ ___/| | ( <_> ) | / ^ / 8 | |___|___| /__| |__|___| /__||__| \___ >______ /\____/|__| \____ | 9 | \/ \/ \/ \/ |__| 10 | ``` 11 | 12 |

13 | 第四代 Infinite QQ 机器人。基于 perpetua-sdk-for-java,为 JavaSE 与 Bukkit 环境下 QQ 机器人的开发提供附属注册、配置管理、事件分发、等功能支持。 14 |

15 | 16 |

17 | 18 | 19 | 20 |

21 | 22 |

23 | [Expansions] 24 | [Document (维护中)] 25 |

26 | 27 | ## Import 28 | 29 | ### Maven 30 | 31 | ```xml 32 | 33 | iunlimit-releases 34 | IllTamer's Repository 35 | https://maven.illtamer.com/releases 36 | 37 | ``` 38 | 39 | ```xml 40 | 41 | com.illtamer.infinite.bot 42 | minecraft 43 | {version} 44 | 45 | ``` 46 | 47 | ### Gradle 48 | 49 | ```groovy 50 | maven { 51 | url "https://maven.illtamer.com/releases" 52 | } 53 | ``` 54 | 55 | ```groovy 56 | implementation 'com.illtamer.infinite.bot:minecraft:{version}' 57 | ``` 58 | 59 | ## 声明 60 | 61 | - 若您在使用时有任何疑问,欢迎入群讨论咨询 `QQ: 863522624` 62 | 63 | - 若您为 Minecraft 公益服主且服务器资源难以承受 perpetua 的运行,欢迎 [[联系我]](https://api.vvhan.com/api/qqCard?qq=765743073) 。我与我的云服务很乐意为您提供一份力所能及的援助。 64 | 65 | ## 致谢 66 | 67 | - 感谢 小豆子、阿丽塔、polar、一口小雨、黑土、仔仔 等腐竹在测试、策划方面提供的帮助与支持 68 | 69 | - 感谢机器人插件的先驱者 [@Albert](https://github.com/mcdoeswhat) -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/factory/SingletonFactory.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.automation.factory; 2 | 3 | import com.illtamer.infinite.bot.minecraft.exception.InitializationException; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.management.openmbean.KeyAlreadyExistsException; 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.util.HashMap; 10 | 11 | /** 12 | * 单例工厂 13 | * */ 14 | public class SingletonFactory { 15 | 16 | private static final HashMap, Object> SINGLETONS = new HashMap<>(1 << 6); 17 | 18 | private SingletonFactory() {} 19 | 20 | /** 21 | * 获取单例对象 22 | * @apiNote 传入类必须有无参构造或已被注册 23 | * */ 24 | @NotNull 25 | public static T getInstance(Class clazz) { 26 | if (clazz == null) 27 | throw new NullPointerException(); 28 | 29 | Object instance = SINGLETONS.get(clazz); 30 | if (instance != null) 31 | return clazz.cast(instance); 32 | 33 | instance = createInstance(clazz); 34 | return clazz.cast(instance); 35 | } 36 | 37 | /** 38 | * 设置单例对象 39 | * */ 40 | public static void setInstance(Class clazz, Object object) { 41 | if (!clazz.isInstance(object)) 42 | throw new InitializationException("Mismatched class and instance"); 43 | if (SINGLETONS.containsKey(clazz)) 44 | throw new KeyAlreadyExistsException(); 45 | SINGLETONS.put(clazz, object); 46 | } 47 | 48 | /** 49 | * 移除单例 50 | * */ 51 | public static T remove(Class clazz) { 52 | return clazz.cast(SINGLETONS.remove(clazz)); 53 | } 54 | 55 | @NotNull 56 | private synchronized static Object createInstance(Class clazz) { 57 | Object instance = SINGLETONS.get(clazz); 58 | if (instance != null) return instance; 59 | try { 60 | Constructor constructor = clazz.getDeclaredConstructor(); 61 | constructor.setAccessible(true); 62 | instance = constructor.newInstance(); 63 | SINGLETONS.put(clazz, instance); 64 | return instance; 65 | } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 66 | throw new InitializationException(e); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ValidUtil.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 6 | import com.illtamer.perpetua.sdk.Pair; 7 | import com.illtamer.perpetua.sdk.util.HttpRequestUtil; 8 | import lombok.experimental.UtilityClass; 9 | import org.bukkit.entity.Player; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.UUID; 13 | 14 | @UtilityClass 15 | public class ValidUtil { 16 | private static final String SESSION_SERVER = "https://sessionserver.mojang.com/session/minecraft/profile/"; 17 | private static final Gson GSON = new Gson(); 18 | 19 | public static boolean isValidPlayer(@NotNull Player player) { 20 | return isValid(player.getUniqueId(), player.getName()); 21 | } 22 | 23 | public static boolean isValid(@NotNull UUID uuid, @NotNull String name) { 24 | return isValid(uuid.toString(), name); 25 | } 26 | 27 | public static boolean isValid(@NotNull String uuid, @NotNull String name) { 28 | try { 29 | final Pair pair = HttpRequestUtil.getJson(SESSION_SERVER + uuid, null); 30 | final Integer status = pair.getKey(); 31 | if (status == 400) return false; 32 | if (status != 200) { 33 | BukkitBootstrap.getInstance().getLogger().warning("正版UUID验证服务器不可用,状态码: " + status); 34 | return false; 35 | } 36 | final JsonObject object = GSON.fromJson(pair.getValue(), JsonObject.class); 37 | return name.equals(object.get("name").getAsString()); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | return false; 42 | } 43 | 44 | public static boolean isValidUUID(@NotNull UUID uuid) { 45 | return isValidUUID(uuid.toString()); 46 | } 47 | 48 | public static boolean isValidUUID(@NotNull String uuid) { 49 | try { 50 | final Pair pair = HttpRequestUtil.getJson(SESSION_SERVER + uuid, null); 51 | final Integer status = pair.getKey(); 52 | if (status == 200) return true; 53 | BukkitBootstrap.getInstance().getLogger().warning("正版UUID验证服务器不可用,状态码: " + status); 54 | } catch (Exception e) { 55 | System.err.println(e.getMessage()); 56 | } 57 | return false; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/origin/OriginConfig.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.start.origin; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | // TODO 13 | class OriginConfig implements Configuration { 14 | 15 | @Override 16 | public String getString(String path) { 17 | return null; 18 | } 19 | 20 | @Override 21 | public String getString(String path, String def) { 22 | return null; 23 | } 24 | 25 | @Override 26 | public List getStringList(String path) { 27 | return null; 28 | } 29 | 30 | @Override 31 | public Integer getInt(String path) { 32 | return null; 33 | } 34 | 35 | @Override 36 | public Integer getInt(String path, Integer def) { 37 | return null; 38 | } 39 | 40 | @Override 41 | public Long getLong(String path) { 42 | return null; 43 | } 44 | 45 | @Override 46 | public Long getLong(String path, Long def) { 47 | return null; 48 | } 49 | 50 | @Override 51 | public Boolean getBoolean(String path) { 52 | return null; 53 | } 54 | 55 | @Override 56 | public Boolean getBoolean(String path, Boolean def) { 57 | return null; 58 | } 59 | 60 | @Override 61 | public List getLongList(String path) { 62 | return null; 63 | } 64 | 65 | @Override 66 | public void set(String path, Object value) { 67 | 68 | } 69 | 70 | @Nullable 71 | @Override 72 | public Configuration getSection(String path) { 73 | return null; 74 | } 75 | 76 | @Override 77 | public Configuration createSection(String path, Map data) { 78 | return null; 79 | } 80 | 81 | @Override 82 | public String saveToString() { 83 | return null; 84 | } 85 | 86 | @Override 87 | public Set getKeys(boolean deep) { 88 | return null; 89 | } 90 | 91 | @Override 92 | public Map getValues(boolean deep) { 93 | return null; 94 | } 95 | 96 | @Override 97 | public void save(File file) throws IOException { 98 | 99 | } 100 | 101 | @Override 102 | public void load(File file) throws IOException { 103 | 104 | } 105 | 106 | @Override 107 | public void load(String yaml) { 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/Registration.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.automation; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.infinite.bot.minecraft.exception.InitializationException; 5 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionConfig; 6 | import com.illtamer.infinite.bot.minecraft.expansion.automation.factory.SingletonFactory; 7 | import lombok.SneakyThrows; 8 | import org.bukkit.configuration.file.FileConfiguration; 9 | import org.bukkit.configuration.serialization.ConfigurationSerialization; 10 | 11 | import java.util.HashMap; 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | /** 17 | * Enum 字段在注册前应重写 #toString() 和 #valueOf(), 以便于正确进行序列化和反序列化 18 | * */ 19 | public class Registration { 20 | 21 | private static final Map>> autoConfigMap = new HashMap<>(); 22 | 23 | /** 24 | * 注册自动配置类 25 | * @apiNote 请在 onEnable 方法中调用 26 | * */ 27 | @SneakyThrows 28 | public static void add(AutoLoadConfiguration instance, IExpansion expansion) { 29 | final Class clazz = instance.getClass(); 30 | final Set> set = autoConfigMap.computeIfAbsent(expansion, k -> new HashSet<>()); 31 | if (set.contains(clazz)) 32 | throw new IllegalArgumentException("Duplicate auto-config class: " + clazz); 33 | set.add(clazz); 34 | ConfigurationSerialization.registerClass(clazz); 35 | SingletonFactory.setInstance(clazz, instance); 36 | } 37 | 38 | /** 39 | * 获取自动配置类单例 40 | * */ 41 | public static T get(Class clazz) { 42 | try { 43 | return SingletonFactory.getInstance(clazz); 44 | } catch (InitializationException e) { 45 | throw new IllegalArgumentException("未注册的自动配置类: " + clazz); 46 | } 47 | } 48 | 49 | /** 50 | * 注销并保存所有自动配置文件 51 | * */ 52 | public static void removeAndStoreAutoConfigs(IExpansion expansion) { 53 | final Set> set = autoConfigMap.remove(expansion); 54 | if (set == null || set.size() == 0) return; 55 | for (Class clazz : set) { 56 | final AutoLoadConfiguration configuration = SingletonFactory.remove(clazz); 57 | final ExpansionConfig configFile = configuration.getConfigFile(); 58 | final FileConfiguration config = configFile.getConfig(); 59 | for (Map.Entry entry : configuration.serialize().entrySet()) { 60 | config.set(entry.getKey(), entry.getValue()); 61 | } 62 | configFile.save(); 63 | ConfigurationSerialization.unregisterClass(clazz); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 4 | import lombok.experimental.UtilityClass; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.logging.Logger; 11 | 12 | @UtilityClass 13 | public class StringUtil { 14 | 15 | private static final Logger log = BukkitBootstrap.getInstance().getLogger(); 16 | 17 | /** 18 | * @return aaa\nbbb\nccc 19 | * */ 20 | public static String toString(List list) { 21 | StringBuilder builder = new StringBuilder(); 22 | for (String s : list) 23 | builder.append('\n').append(s); 24 | builder.deleteCharAt(0); 25 | return builder.toString(); 26 | } 27 | 28 | public static String[] toArray(List list) { 29 | String[] array = new String[list.size()]; 30 | for(int i = 0; i < list.size(); ++i) 31 | array[i] = list.get(i); 32 | return array; 33 | } 34 | 35 | /** 36 | * @return [obj1, obj2] 37 | * */ 38 | public static String parseString(List list) { 39 | if (list == null) return null; 40 | if (list.size() == 0) return "[]"; 41 | StringBuilder builder = new StringBuilder(); 42 | builder.append('['); 43 | for (int i = 0; i < list.size(); i++) { 44 | if (i != 0) builder.append(", "); 45 | builder.append(list.get(i)); 46 | } 47 | builder.append(']'); 48 | return builder.toString(); 49 | } 50 | 51 | /** 52 | * @param list [obj1, obj2] 53 | * */ 54 | @NotNull 55 | public static List parseList(String list) { 56 | List result = new ArrayList<>(); 57 | if (list == null || list.length() <= 2) return result; 58 | try { 59 | final String[] split = list.substring(1, list.length() - 1).split(", "); 60 | result.addAll(Arrays.asList(split)); 61 | } catch (Exception e) { 62 | log.warning("Some errors occurred in the process of parse '" + list + "'"); 63 | e.printStackTrace(); 64 | } 65 | return result; 66 | } 67 | 68 | /** 69 | * 判断字符串是否为 null、空或仅由空白字符组成。 70 | * 71 | * @param str 要判断的字符串 72 | * @return 如果字符串为 null、空或仅包含空白字符,则返回 true;否则返回 false。 73 | */ 74 | public static boolean isBlank(String str) { 75 | if (str == null || str.isEmpty()) { 76 | return true; 77 | } 78 | // 检查是否全部是空白字符 79 | for (int i = 0; i < str.length(); i++) { 80 | if (!Character.isWhitespace(str.charAt(i))) { 81 | return false; 82 | } 83 | } 84 | return true; 85 | } 86 | 87 | /** 88 | * 判断字符串是否不为 null、不为空且至少包含一个非空白字符。 89 | * 90 | * @param str 要判断的字符串 91 | * @return 如果字符串不为 null、不为空且包含非空白字符,则返回 true;否则返回 false。 92 | */ 93 | public static boolean isNotBlank(String str) { 94 | return !isBlank(str); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/bukkit/BukkitBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.start.bukkit; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler; 4 | import com.illtamer.infinite.bot.minecraft.api.EventExecutor; 5 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI; 6 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 7 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration; 8 | import com.illtamer.infinite.bot.minecraft.configuration.BotNettyHolder; 9 | import com.illtamer.infinite.bot.minecraft.configuration.StatusCheckRunner; 10 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration; 11 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader; 12 | import com.illtamer.infinite.bot.minecraft.listener.BukkitCommandListener; 13 | import com.illtamer.infinite.bot.minecraft.listener.PluginListener; 14 | import com.illtamer.infinite.bot.minecraft.util.Optional; 15 | import lombok.Getter; 16 | import org.bukkit.command.PluginCommand; 17 | import org.bukkit.plugin.java.JavaPlugin; 18 | 19 | // TODO 集中help管理、command支持 20 | // libs folder 21 | public class BukkitBootstrap extends JavaPlugin implements Bootstrap { 22 | 23 | @Getter 24 | private static BukkitBootstrap instance; 25 | 26 | @Getter 27 | private final ExpansionLoader expansionLoader = new ExpansionLoader(this); 28 | @Getter 29 | private final Optional nettyHolder = Optional.empty(); 30 | 31 | @Override 32 | public void onLoad() { 33 | StaticAPI.setInstance(instance = this); 34 | // DependencyLoader.load(instance); 35 | BotConfiguration.load(instance); 36 | this.nettyHolder.set(new BotNettyHolder(getLogger(), EventExecutor::dispatchListener)); 37 | nettyHolder.get().connect(); 38 | } 39 | 40 | @Override 41 | public void onEnable() { 42 | nettyHolder.ifPresent(BotNettyHolder::checkConnection); 43 | BotScheduler.runTaskTimer(new StatusCheckRunner(getLogger()), 2L, 30); 44 | BotScheduler.runTaskLater(() -> expansionLoader.loadExpansions(false), 3L); 45 | BukkitCommandListener bukkitCommandListener = new BukkitCommandListener(); 46 | final PluginCommand command = Optional.ofNullable(getServer().getPluginCommand("InfiniteBot4")) 47 | .orElseThrow(NullPointerException::new); 48 | command.setTabCompleter(bukkitCommandListener); 49 | command.setExecutor(bukkitCommandListener); 50 | getServer().getPluginManager().registerEvents(new PluginListener(this), this); 51 | } 52 | 53 | @Override 54 | public void onDisable() { 55 | BotScheduler.close(); 56 | expansionLoader.disableExpansions(false); 57 | BotConfiguration.saveAndClose(); 58 | nettyHolder.ifPresent(BotNettyHolder::close); 59 | instance = null; 60 | } 61 | 62 | @Override 63 | public Configuration createConfig() { 64 | return new BukkitConfigSection.Config(); 65 | } 66 | 67 | @Override 68 | public ClassLoader getInstClassLoader() { 69 | return getClassLoader(); 70 | } 71 | 72 | @Override 73 | public Type getType() { 74 | return Type.BUKKIT; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /minecraft/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | // id 'dev.vankka.dependencydownload.plugin' version '1.3.1' 3 | id 'com.github.johnrengelman.shadow' version '7.0.0' 4 | } 5 | 6 | dependencies { 7 | implementation 'dev.vankka:dependencydownload-runtime:1.3.1' 8 | 9 | api 'com.illtamer.perpetua.sdk:perpetua-sdk:0.2.2-SNAPSHOT' 10 | api 'org.slf4j:slf4j-api:1.7.32' 11 | implementation 'mysql:mysql-connector-java:8.0.29' 12 | implementation 'com.zaxxer:HikariCP:4.0.3' 13 | implementation 'org.slf4j:slf4j-api:1.7.32' 14 | implementation 'org.slf4j:slf4j-simple:1.7.32' 15 | 16 | compileOnly 'org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT' 17 | compileOnly 'net.md-5:bungeecord-api:1.21-R0.1' 18 | compileOnly 'org.projectlombok:lombok:1.18.26' 19 | annotationProcessor 'org.projectlombok:lombok:1.18.26' 20 | } 21 | 22 | tasks.withType(Javadoc).configureEach { 23 | enabled = false 24 | } 25 | 26 | publishing { 27 | repositories { 28 | if (version.toString().endsWith("-SNAPSHOT")) { 29 | maven { 30 | name "iunlimitSnapshots" 31 | url "https://maven.illtamer.com/snapshots" 32 | credentials(PasswordCredentials) 33 | authentication { 34 | basic(BasicAuthentication) 35 | } 36 | } 37 | } else { 38 | maven { 39 | name = "iunlimitReleases" 40 | url = "https://maven.illtamer.com/releases" 41 | credentials(PasswordCredentials) 42 | authentication { 43 | basic(BasicAuthentication) 44 | } 45 | } 46 | } 47 | } 48 | publications { 49 | mavenJava(MavenPublication) { 50 | groupId = group 51 | artifactId = project.name 52 | version = version 53 | from components.java 54 | } 55 | } 56 | } 57 | 58 | processResources { 59 | filteringCharset = 'UTF-8' 60 | filesMatching('plugin.yml') { 61 | expand('version': project.version) 62 | } 63 | } 64 | 65 | // generate runtimeDownloadOnly.txt 66 | //shadowJar.dependsOn generateRuntimeDownloadResourceForRuntimeDownloadOnly, generateRuntimeDownloadResourceForRuntimeDownload 67 | 68 | shadowJar { 69 | archiveBaseName = parent.name + '-' + project.name 70 | version = project.version 71 | archiveClassifier = 'all' 72 | manifest { 73 | attributes('Automatic-Module-Name': 'com.illtamer.infinite.bot.minecraft') 74 | } 75 | } 76 | 77 | [ 78 | 'io.netty', 79 | // 'org.apache.httpcomponents', 80 | // 'commons-logging', 81 | // 'commons-codec', 82 | 'com.google.gson', 83 | // 'com.google.guava', 84 | // com.google.guava 85 | // 'com.google.common', 86 | // 'com.google.code.findbugs', 87 | // 'com.google.errorprone', 88 | // 'com.google.j2objc', 89 | // 'org.checkerframework', 90 | // 'com.google.protobuf', 91 | // 'org.jetbrains', 92 | // 'mysql', 93 | // 'org.slf4j', 94 | ].each { 95 | var relocated = 'com.illtamer.infinite.bot.minecraft.libs.' + it 96 | tasks.shadowJar.relocate it, relocated 97 | // tasks.generateRuntimeDownloadResourceForRuntimeDownloadOnly.relocate it, relocated 98 | } 99 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/manager/AbstractExternalExpansion.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.manager; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion; 4 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration; 5 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLogger; 6 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 7 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil; 8 | import com.illtamer.perpetua.sdk.util.Assert; 9 | import org.bukkit.plugin.Plugin; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.slf4j.Logger; 12 | 13 | import java.io.File; 14 | import java.io.InputStream; 15 | import java.util.Objects; 16 | 17 | public abstract class AbstractExternalExpansion implements IExternalExpansion { 18 | 19 | private final ClassLoader classLoader; 20 | private final File dataFolder; 21 | private final ExpansionLogger logger; 22 | 23 | private boolean register; 24 | private boolean enabled; 25 | 26 | public AbstractExternalExpansion() { 27 | this.classLoader = this.getClass().getClassLoader(); 28 | Assert.notEmpty(getExpansionName(), "Expansion name can not be empty!"); 29 | this.dataFolder = new File(BukkitBootstrap.getInstance().getDataFolder(), '/' + BotConfiguration.EXPANSION_FOLDER_NAME + '/' + getExpansionName()); 30 | this.logger = new ExpansionLogger(this); 31 | } 32 | 33 | @Override 34 | public void register(@NotNull Plugin plugin) { 35 | BukkitBootstrap.getInstance().getExpansionLoader().loadExternalExpansion(this, plugin); 36 | register = true; 37 | } 38 | 39 | @Override 40 | public void unregister() { 41 | BukkitBootstrap.getInstance().getExpansionLoader().disableExternalExpansion(this); 42 | register = false; 43 | } 44 | 45 | /** 46 | * bot内部开启/关闭拓展 47 | * */ 48 | protected void setEnabled(boolean enabled) { 49 | if (this.enabled != enabled) { 50 | this.enabled = enabled; 51 | if (enabled) { 52 | onEnable(); 53 | } else { 54 | onDisable(); 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public Logger getLogger() { 61 | return logger; 62 | } 63 | 64 | @Override 65 | public File getDataFolder() { 66 | return dataFolder; 67 | } 68 | 69 | @Override 70 | public InputStream getResource(String name) { 71 | return ExpansionUtil.getPluginResource(name, classLoader); 72 | } 73 | 74 | @Override 75 | public void saveResource(String path, boolean replace) { 76 | ExpansionUtil.savePluginResource(path, replace, dataFolder, this::getResource); 77 | } 78 | 79 | @Override 80 | public boolean isEnabled() { 81 | return enabled; 82 | } 83 | 84 | @Override 85 | public boolean isRegister() { 86 | return register; 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return Objects.hash(getExpansionName(), getVersion(), getAuthor()); 92 | } 93 | 94 | @Override 95 | @NotNull 96 | public String toString() { 97 | return ExpansionUtil.formatIdentifier(this); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/DependencyLoader.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 5 | import com.illtamer.infinite.bot.minecraft.util.ProcessBar; 6 | import dev.vankka.dependencydownload.DependencyManager; 7 | import dev.vankka.dependencydownload.repository.Repository; 8 | import dev.vankka.dependencydownload.repository.StandardRepository; 9 | import lombok.SneakyThrows; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.InputStreamReader; 14 | import java.lang.reflect.Method; 15 | import java.net.URL; 16 | import java.net.URLClassLoader; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * 依赖库动态加载器 25 | * */ 26 | public class DependencyLoader { 27 | 28 | public static final List REPOSITORIES = Arrays.asList( 29 | new StandardRepository("https://repo.maven.apache.org/maven2"), 30 | new StandardRepository("https://oss.sonatype.org/content/repositories/snapshots"), 31 | new StandardRepository("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") 32 | ); 33 | 34 | @SneakyThrows 35 | public static void load(Bootstrap instance) { 36 | File dependencyFolder = new File(instance.getDataFolder(), "libs"); 37 | if (!dependencyFolder.exists()) dependencyFolder.mkdirs(); 38 | ExecutorService executor = BotScheduler.IO_INTENSIVE; 39 | 40 | DependencyManager manager = new DependencyManager(dependencyFolder.toPath()); 41 | String resource = new BufferedReader(new InputStreamReader(instance.getResource("runtimeDownload.txt"))) 42 | .lines().collect(Collectors.joining(System.lineSeparator())); 43 | manager.loadFromResource(resource); 44 | System.out.println("getRelocations: " + manager.getRelocations().size()); 45 | 46 | instance.getLogger().info("Download dependencies ..."); 47 | CompletableFuture[] downloadFutures = manager.download(executor, REPOSITORIES); 48 | ProcessBar downloadBar = ProcessBar.create(downloadFutures.length); 49 | for (CompletableFuture future : downloadFutures) { 50 | executor.submit(() -> { 51 | future.join(); 52 | downloadBar.count(); 53 | }); 54 | } 55 | 56 | manager.relocateAll(executor).join(); 57 | instance.getLogger().info("Loading dependencies ..."); 58 | CompletableFuture[] loadFutures = manager.load(executor, (path) -> { 59 | try { 60 | URLClassLoader classLoader = (URLClassLoader) instance.getInstClassLoader(); 61 | Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); 62 | addURL.setAccessible(true); 63 | addURL.invoke(classLoader, path.toUri().toURL()); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | }); 68 | // ProcessBar loadBar = ProcessBar.create(loadFutures.length); 69 | for (CompletableFuture future : loadFutures) { 70 | executor.submit(() -> { 71 | future.join(); 72 | // loadBar.count(); 73 | }); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/pojo/TimedBlockingCache.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.pojo; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.TimeoutException; 8 | import java.util.concurrent.locks.Condition; 9 | import java.util.concurrent.locks.ReentrantLock; 10 | 11 | /** 12 | * TimedBlockingCache 13 | * @apiNote 基于LinkedHashMap实现FIFO容量控制,并使用 ReentrantLock 和 Condition 实现阻塞等待和超时机制 14 | * */ 15 | public class TimedBlockingCache { 16 | 17 | @Getter 18 | private final int capacity; 19 | private final LinkedHashMap map; 20 | private final ReentrantLock lock = new ReentrantLock(); 21 | private final Map waitConditions = new HashMap<>(); 22 | 23 | public TimedBlockingCache(int capacity) { 24 | if (capacity <= 0) { 25 | throw new IllegalArgumentException("Capacity must be positive"); 26 | } 27 | this.capacity = capacity; 28 | this.map = new LinkedHashMap(capacity, 0.75f, true) { 29 | @Override 30 | protected boolean removeEldestEntry(Map.Entry eldest) { 31 | return size() > capacity; 32 | } 33 | }; 34 | } 35 | 36 | public void put(K key, V value) { 37 | if (value == null) { 38 | throw new NullPointerException("Value cannot be null"); 39 | } 40 | lock.lock(); 41 | try { 42 | map.put(key, value); 43 | // 如果有线程在等待这个key,唤醒它们 44 | if (waitConditions.containsKey(key)) { 45 | waitConditions.get(key).signalAll(); 46 | } 47 | } finally { 48 | lock.unlock(); 49 | } 50 | } 51 | 52 | public V get(K key, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { 53 | long timeoutMillis = unit.toMillis(timeout); 54 | lock.lock(); 55 | try { 56 | if (map.containsKey(key)) { 57 | return map.get(key); 58 | } 59 | 60 | // 创建或获取等待条件 61 | Condition condition = waitConditions.get(key); 62 | if (condition == null) { 63 | condition = lock.newCondition(); 64 | waitConditions.put(key, condition); 65 | } 66 | 67 | // 等待超时 68 | boolean timedOut = !condition.await(timeoutMillis, TimeUnit.MILLISECONDS); 69 | if (timedOut) { 70 | // 超时后移除等待条件 71 | waitConditions.remove(key); 72 | throw new TimeoutException("Timeout waiting for key: " + key); 73 | } 74 | 75 | // 被唤醒后检查数据 76 | if (map.containsKey(key)) { 77 | return map.get(key); 78 | } else { 79 | // 理论上不会发生(唤醒时数据应存在) 80 | throw new TimeoutException("Unexpected timeout for key: " + key); 81 | } 82 | } finally { 83 | lock.unlock(); 84 | } 85 | } 86 | 87 | public boolean remove(K key) { 88 | lock.lock(); 89 | try { 90 | boolean exists = map.containsKey(key); 91 | if (exists) { 92 | map.remove(key); 93 | // 移除等待条件 94 | waitConditions.remove(key); 95 | } 96 | return exists; 97 | } finally { 98 | lock.unlock(); 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/util/ExpansionUtil.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.util; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.infinite.bot.minecraft.pojo.ExpansionIdentifier; 5 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 6 | import com.illtamer.perpetua.sdk.util.Assert; 7 | import lombok.experimental.UtilityClass; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.io.*; 12 | import java.net.URL; 13 | import java.net.URLConnection; 14 | import java.util.function.Function; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | @UtilityClass 19 | public class ExpansionUtil { 20 | 21 | public static final Pattern IDENTIFIER = Pattern.compile("([^-]*)-(.*)::(.*)"); 22 | public static final String FORMAT = "%s-%s::%s"; 23 | 24 | @NotNull 25 | public static String formatIdentifier(IExpansion expansion) { 26 | return formatIdentifier(expansion.getExpansionName(), expansion.getVersion(), expansion.getAuthor()); 27 | } 28 | 29 | @NotNull 30 | public static String formatIdentifier(String name, String version, String author) { 31 | return String.format(FORMAT, name, version, author); 32 | } 33 | 34 | @Nullable 35 | public static ExpansionIdentifier parseIdentifier(String identifier) { 36 | final Matcher matcher = IDENTIFIER.matcher(identifier); 37 | if (matcher.find()) { 38 | return new ExpansionIdentifier(matcher.group(1), matcher.group(2), matcher.group(3)); 39 | } 40 | return null; 41 | } 42 | 43 | @Nullable 44 | public static InputStream getPluginResource(String name, ClassLoader classLoader) { 45 | URL url = classLoader.getResource(name); 46 | if (url == null) { 47 | return null; 48 | } 49 | try { 50 | URLConnection connection = url.openConnection(); 51 | connection.setUseCaches(false); 52 | return connection.getInputStream(); 53 | } catch (IOException e) { 54 | return null; 55 | } 56 | } 57 | 58 | public static void savePluginResource(String path, boolean replace, File dataFolder, Function inputFunc) { 59 | Assert.isTrue(path != null && !path.isEmpty(), "The resource name can not be null !"); 60 | path = path.replace("\\", "/"); 61 | InputStream input = inputFunc.apply(path); 62 | Assert.notNull(input, String.format("Can't find the resource '%s'", path)); 63 | 64 | File outFile = new File(dataFolder, path); 65 | int lastIndex = path.lastIndexOf('/'); 66 | File outDir = new File(dataFolder, path.substring(0, Math.max(lastIndex, 0))); 67 | if (!outDir.exists()) { 68 | outDir.mkdirs(); 69 | } 70 | try { 71 | if (!outFile.exists() || replace) { 72 | OutputStream out = new FileOutputStream(outFile); 73 | byte[] buf = new byte[1024]; 74 | int len; 75 | while ((len = input.read(buf)) > 0) { 76 | out.write(buf, 0, len); 77 | } 78 | out.close(); 79 | input.close(); 80 | } else { 81 | BukkitBootstrap.getInstance().getLogger().warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists !"); 82 | } 83 | } catch (IOException ex) { 84 | BukkitBootstrap.getInstance().getLogger().severe("Could not save " + outFile.getName() + " to " + outFile); 85 | ex.printStackTrace(); 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/manager/InfiniteExpansion.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.manager; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration; 5 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLogger; 6 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 7 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.io.File; 11 | import java.io.InputStream; 12 | import java.util.Objects; 13 | 14 | public abstract class InfiniteExpansion implements IExpansion { 15 | private boolean enabled = false; 16 | private InfinitePluginLoader loader; 17 | private File jarFile; 18 | private ClassLoader classLoader; 19 | private ExpansionLogger logger; 20 | private File dataFolder; 21 | 22 | public InfiniteExpansion() { 23 | ClassLoader classLoader = getClass().getClassLoader(); 24 | if (!(classLoader instanceof PluginClassLoader)) { 25 | throw new IllegalStateException("InfiniteExpansion requires " + PluginClassLoader.class.getName()); 26 | } 27 | ((PluginClassLoader)classLoader).initialize(this); 28 | } 29 | 30 | protected InfiniteExpansion(InfinitePluginLoader loader, File jarFile, String folderName) { 31 | this.loader = loader; 32 | ClassLoader classLoader = getClass().getClassLoader(); 33 | if (classLoader instanceof PluginClassLoader) { 34 | throw new IllegalStateException("Cannot use initialization constructor at runtime"); 35 | } 36 | init(loader, jarFile, classLoader, folderName); 37 | } 38 | 39 | @Override 40 | public boolean isEnabled() { 41 | return enabled; 42 | } 43 | 44 | final void init(InfinitePluginLoader loader, File jarFile, ClassLoader classLoader, String folderName) { 45 | this.loader = loader; 46 | this.jarFile = jarFile; 47 | this.classLoader = classLoader; 48 | this.logger = new ExpansionLogger(this); 49 | folderName = getExpansionName() != null && getExpansionName().length() != 0 ? getExpansionName() : folderName; 50 | this.dataFolder = new File(BukkitBootstrap.getInstance().getDataFolder(), '/' + BotConfiguration.EXPANSION_FOLDER_NAME + '/' + folderName); 51 | } 52 | 53 | /** 54 | * bot内部开启/关闭拓展 55 | * */ 56 | protected void setEnabled(boolean enabled) { 57 | if (this.enabled != enabled) { 58 | this.enabled = enabled; 59 | if (enabled) { 60 | onEnable(); 61 | } else { 62 | onDisable(); 63 | } 64 | } 65 | } 66 | 67 | protected ClassLoader getClassLoader() { 68 | return classLoader; 69 | } 70 | 71 | @Override 72 | public InputStream getResource(String name) { 73 | return ExpansionUtil.getPluginResource(name, classLoader); 74 | } 75 | 76 | @Override 77 | public void saveResource(String path, boolean replace) { 78 | ExpansionUtil.savePluginResource(path, replace, dataFolder, this::getResource); 79 | } 80 | 81 | @Override 82 | public File getDataFolder() { 83 | return dataFolder; 84 | } 85 | 86 | @Override 87 | public ExpansionLogger getLogger() { 88 | return logger; 89 | } 90 | 91 | @Override 92 | public int hashCode() { 93 | return Objects.hash(getExpansionName(), getVersion(), getAuthor()); 94 | } 95 | 96 | @Override 97 | @NotNull 98 | public String toString() { 99 | return ExpansionUtil.formatIdentifier(this); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /docs/zh-cn/dev-api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## 导入 4 | 5 | ### Maven 6 | 7 | InfiniteBot-v3 为支持 go-cqhttp 实现了一系列包括事件监听、消息回调、end-point 快速操作等 API 8 | 。您可将 [[api]](/api) 模块作为调用 go-cqhttp 的前置依赖库导入项目,进行开发。 9 | 10 | ```xml 11 | 12 | com.illtamer.infinite.bot 13 | api 14 | 1.0.5 15 | 16 | ``` 17 | 18 | 毫无疑问,IB3 需要您提供您开启的 go-cqhttp 服务的详细参数以便于与机器人建立连接。请在程序中调用以下函数以便于初始化连接。 19 | 20 | ```java 21 | CQHttpWebSocketConfiguration.start(httpUri, wsUri, authorization, eventConsumer); 22 | ``` 23 | 24 | 该方法最后一个参数为事件基类 [Event](/api/src/main/java/com/illtamer/infinite/bot/api/event/Event.java) 的 `Consumer` 对象,即事件的处理函数。 25 | 26 | > 在 Spring 框架中,您可以调用 `ApplicationEventPublisher#publish(Object)` 将事件托管。 27 | 28 | 29 | ### SpringBoot 30 | 31 | InfiniteBot-v3 额外优化了在 SpringBoot 框架下的开发体验。您只需要进行相应配置即可使用 32 | 33 | 1. 导入 ib3-spring-boot-starter 34 | 35 | ```xml 36 | 37 | com.illtamer.infinite.bot 38 | ib3-spring-boot-starter 39 | 1.0.5 40 | 41 | ``` 42 | 43 | 2. 在`application.yml`填写以下配置节点 44 | 45 | ```yaml 46 | bot: 47 | http-uri: '' 48 | ws-uri: '' 49 | authorization: '' 50 | ``` 51 | 52 | ## 示例 53 | 54 | !> InfiniteBot3 暂未计划支持任何频道 API 55 | 56 | ### Message 57 | 58 | go-cqhttp 同时支持使用两类消息 —— CQ码与Json,故 InfiniteBot3 也分别支持两种消息的构建。两类消息分别对应实体类 `CQMessage` 与 `JsonMessage`,他们都有一个共同的抽象父类 `Message` 作为类型声明。 59 | 60 | > 在与 go-cqhttp 的 WebSocket 长连接中,事件中的消息以CQ码的形式被传递与解析。一般的,我们使用Json这种层次分明的数据结构来构建需要发送的消息对象。 61 | 62 | #### 生成 63 | 64 | 您可以使用 `MessageBuilder` 建造者工具类来生成一个 `Message` 实例: 65 | 66 | ```java 67 | Message message = MessageBuilder.json() 68 | .text("Hello World") 69 | .build(); 70 | ``` 71 | 72 | #### Message Chain 73 | 74 | 在 `Message` 被构造的过程中,其内部还会维护一个 `MessageChain` 对象来描述消息中各组成的类型 `TransferEntity`。[点击查看]((https://github.com/IllTamer/infinitebot3/blob/main/api/src/main/java/com/illtamer/infinite/bot/api/entity/transfer/))支持的类型 75 | 76 | ### Event Channel 77 | 78 | ?> _TODO_ 事件管道相关 API 正在开发中 ~ 79 | 80 | ### Web API 81 | 82 | api 模块已内置部分较为常用的 go-cqhttp Web API,您可通过 [[OpenAPIHandling]](https://github.com/IllTamer/infinitebot3/blob/main/api/src/main/java/com/illtamer/infinite/bot/api/handler/OpenAPIHandling.java) 便捷调用所有已支持的 API,或查看其内部实现。 83 | 84 | #### 自定义 APIHandler 85 | 86 | 1. 继承 [[AbstractAPIHandler]](https://github.com/IllTamer/infinitebot3/blob/main/api/src/main/java/com/illtamer/infinite/bot/api/handler/AbstractAPIHandler.java) 87 | 88 | 2. 赋予泛型正确的类型(go-cqhttp Http 接口的返回数据类型) 89 | 90 | 3. 向父类构造器中传入正确的 `endpoint` (相关信息请见 [请求说明](https://docs.go-cqhttp.org/api/#%E8%AF%B7%E6%B1%82%E8%AF%B4%E6%98%8E)) 91 | 92 | 4. 将 API 所需参数作为成员变量声明并赋值 93 | 94 | 最终您实现的 APIHandler 应类似以下格式,使用 `APIHandler#request` 方法调用相关 Web API 95 | 96 | ```java 97 | /** 98 | * 获取陌生人信息 99 | * */ 100 | @Getter 101 | public class StrangerGetHandler extends AbstractAPIHandler> { 102 | /** 103 | * QQ 号 104 | * */ 105 | @SerializedName("user_id") 106 | private Long userId; 107 | /** 108 | * 是否不使用缓存(使用缓存可能更新不及时, 但响应更快) 109 | * */ 110 | @SerializedName("no_cache") 111 | private Boolean noCache; 112 | 113 | public StrangerGetHandler() { 114 | super("/get_stranger_info"); 115 | } 116 | 117 | public StrangerGetHandler setUserId(Long userId) { 118 | this.userId = userId; 119 | return this; 120 | } 121 | 122 | public StrangerGetHandler setNoCache(Boolean noCache) { 123 | this.noCache = noCache; 124 | return this; 125 | } 126 | 127 | @NotNull 128 | public static Stranger parse(@NotNull Response> response) { 129 | final Gson gson = new Gson(); 130 | return gson.fromJson(gson.toJson(response.getData()), Stranger.class); 131 | } 132 | } 133 | ``` 134 | 135 | ### Util 136 | 137 | - AdapterUtil 138 | 139 | go-cqhttp 兼容性工具类 140 | 141 | - Assert 142 | 143 | 断言工具类 144 | 145 | - ClassUtil 146 | 147 | 反射工具类 148 | 149 | - HttpRequestUtil 150 | 151 | http 工具类 152 | 153 | - Maps 154 | 155 | 低版本 `Map#of` 实现 -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/StaticAPI.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.configuration.BotNettyHolder; 5 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration; 6 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader; 7 | import com.illtamer.infinite.bot.minecraft.pojo.ExpansionIdentifier; 8 | import com.illtamer.infinite.bot.minecraft.repository.PlayerDataRepository; 9 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 10 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil; 11 | import com.illtamer.perpetua.sdk.entity.transfer.entity.Client; 12 | import com.illtamer.perpetua.sdk.util.Assert; 13 | import lombok.Getter; 14 | import lombok.Setter; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | public class StaticAPI { 19 | 20 | /** 21 | * -- GETTER -- 22 | * 获取当前启动器实例 23 | * 24 | */ 25 | @Setter 26 | @Getter 27 | private static Bootstrap instance; 28 | 29 | @Getter 30 | private static final Client client = new Client(); 31 | 32 | public static boolean isAdmin(long userId) { 33 | return BotConfiguration.main.admins.contains(userId); 34 | } 35 | 36 | public static boolean inGroups(long groupId) { 37 | return BotConfiguration.main.groups.contains(groupId); 38 | } 39 | 40 | /** 41 | * 重连 perpetua WebSocket 服务 42 | * */ 43 | public static void reconnected() { 44 | BukkitBootstrap inst = BukkitBootstrap.getInstance(); 45 | Assert.notNull(inst, "Bukkit plugin instance is null, is there a bukkit mode ?"); 46 | inst.getNettyHolder().ifPresent(BotNettyHolder::connect); 47 | } 48 | 49 | /** 50 | * 查询是否存在指定名称的附属 51 | * @param name 附属名,若注册时未指定则为启动类的全限定名称 52 | * */ 53 | @Deprecated 54 | public static boolean hasExpansion(String name) { 55 | return false; 56 | } 57 | 58 | /** 59 | * 查询是否存在指定名称与作者的附属 60 | * @param name 附属名,若注册时未指定则为启动类的全限定名称 61 | * @param author 作者名称 62 | * */ 63 | public static boolean hasExpansion(@NotNull String name, @NotNull String author) { 64 | return getExpansion(name, author) != null; 65 | } 66 | 67 | /** 68 | * 查询是否存在指定名称、作者与版本的附属 69 | * @param name 附属名,若注册时未指定则为启动类的全限定名称 70 | * @param version 指定版本 71 | * @param author 作者名称 72 | * */ 73 | public static boolean hasExpansion(@NotNull String name, @NotNull String version, @NotNull String author) { 74 | return getExpansion(name, version, author) != null; 75 | } 76 | 77 | /** 78 | * 获取指定名称与作者的附属 79 | * @param name 附属名,若注册时未指定则为启动类的全限定名称 80 | * @param author 作者名称 81 | */ 82 | @Nullable 83 | public static IExpansion getExpansion(@NotNull String name, @NotNull String author) { 84 | final ExpansionLoader loader = BukkitBootstrap.getInstance().getExpansionLoader(); 85 | for (String identifier : loader.getExpansionKeySet()){ 86 | if (!identifier.startsWith(name)) continue; 87 | final ExpansionIdentifier ei = ExpansionUtil.parseIdentifier(identifier); 88 | Assert.notNull(ei, "Unauthenticated identifier: " + identifier); 89 | if (ei.getName().equals(name) && ei.getAuthor().equals(author)) 90 | return loader.getExpansion(identifier); 91 | } 92 | return null; 93 | } 94 | 95 | /** 96 | * 获取指定名称、作者与版本的附属 97 | * @param name 附属名,若注册时未指定则为启动类的全限定名称 98 | * @param version 指定版本 99 | * @param author 作者名称 100 | * */ 101 | public static IExpansion getExpansion(@NotNull String name, @NotNull String version, @NotNull String author) { 102 | return BukkitBootstrap.getInstance().getExpansionLoader().getExpansion(ExpansionUtil.formatIdentifier(name, version, author)); 103 | } 104 | 105 | /** 106 | * 获取 PlayerData 持久层实例 107 | * */ 108 | public static PlayerDataRepository getRepository() { 109 | return BotConfiguration.getInstance().getRepository(); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/start/bukkit/BukkitConfigSection.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.start.bukkit; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.ConfigSection; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration; 5 | import com.illtamer.infinite.bot.minecraft.util.Lambda; 6 | import org.bukkit.configuration.ConfigurationSection; 7 | import org.bukkit.configuration.InvalidConfigurationException; 8 | import org.bukkit.configuration.file.YamlConfiguration; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | class BukkitConfigSection implements ConfigSection { 18 | 19 | protected final ConfigurationSection section; 20 | 21 | private BukkitConfigSection(ConfigurationSection section) { 22 | this.section = section; 23 | } 24 | 25 | @Override 26 | public String getString(String path) { 27 | return section.getString(path); 28 | } 29 | 30 | @Override 31 | public String getString(String path, String def) { 32 | return section.getString(path, def); 33 | } 34 | 35 | @Override 36 | public List getStringList(String path) { 37 | return section.getStringList(path); 38 | } 39 | 40 | @Override 41 | public Integer getInt(String path) { 42 | return section.getInt(path); 43 | } 44 | 45 | @Override 46 | public Integer getInt(String path, Integer def) { 47 | return section.getInt(path, def); 48 | } 49 | 50 | @Override 51 | public Long getLong(String path) { 52 | return section.getLong(path); 53 | } 54 | 55 | @Override 56 | public Long getLong(String path, Long def) { 57 | return section.getLong(path, def); 58 | } 59 | 60 | @Override 61 | public Boolean getBoolean(String path) { 62 | return section.getBoolean(path); 63 | } 64 | 65 | @Override 66 | public Boolean getBoolean(String path, Boolean def) { 67 | return section.getBoolean(path, def); 68 | } 69 | 70 | @Override 71 | public List getLongList(String path) { 72 | return section.getLongList(path); 73 | } 74 | 75 | @Override 76 | public void set(String path, Object value) { 77 | section.set(path, value); 78 | } 79 | 80 | @Override 81 | public Set getKeys(boolean deep) { 82 | return section.getKeys(deep); 83 | } 84 | 85 | @Override 86 | public Map getValues(boolean deep) { 87 | return section.getValues(deep); 88 | } 89 | 90 | static class Config extends BukkitConfigSection implements Configuration { 91 | 92 | private final YamlConfiguration yaml; 93 | 94 | Config() { 95 | super(new YamlConfiguration()); 96 | this.yaml = (YamlConfiguration) super.section; 97 | } 98 | 99 | @Nullable 100 | @Override 101 | public ConfigSection getSection(String path) { 102 | return Lambda.nullableInvoke(BukkitConfigSection::new, yaml.getConfigurationSection(path)); 103 | } 104 | 105 | @Override 106 | public ConfigSection createSection(String path, Map data) { 107 | return new BukkitConfigSection(yaml.createSection(path, data)); 108 | } 109 | 110 | @Override 111 | public String saveToString() { 112 | return yaml.saveToString(); 113 | } 114 | 115 | @Override 116 | public void save(File file) throws IOException { 117 | yaml.save(file); 118 | } 119 | 120 | @Override 121 | public void load(File file) throws IOException { 122 | try { 123 | yaml.load(file); 124 | } catch (InvalidConfigurationException e) { 125 | throw new RuntimeException(e); 126 | } 127 | } 128 | 129 | @Override 130 | public void load(String yaml) { 131 | try { 132 | this.yaml.load(yaml); 133 | } catch (Exception e) { 134 | throw new RuntimeException(e); 135 | } 136 | } 137 | 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/BotNettyHolder.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler; 4 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI; 5 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration; 6 | import com.illtamer.perpetua.sdk.event.Event; 7 | import com.illtamer.perpetua.sdk.handler.OpenAPIHandling; 8 | import com.illtamer.perpetua.sdk.websocket.OneBotConnection; 9 | 10 | import java.lang.invoke.MethodHandle; 11 | import java.lang.invoke.MethodHandles; 12 | import java.lang.invoke.MethodType; 13 | import java.lang.reflect.Field; 14 | import java.util.concurrent.LinkedBlockingQueue; 15 | import java.util.concurrent.ThreadPoolExecutor; 16 | import java.util.concurrent.TimeUnit; 17 | import java.util.function.Consumer; 18 | import java.util.logging.Logger; 19 | 20 | public class BotNettyHolder { 21 | 22 | protected ThreadPoolExecutor websocketExecutor = new ThreadPoolExecutor(1, 1, 23 | 0L, TimeUnit.MILLISECONDS, 24 | new LinkedBlockingQueue<>(1)); 25 | 26 | private final Consumer interruptConsumer; 27 | private final Logger logger; 28 | private final Consumer eventConsumer; 29 | 30 | public BotNettyHolder(Logger logger, Consumer eventConsumer) { 31 | this.logger = logger; 32 | this.eventConsumer = eventConsumer; 33 | } 34 | 35 | /** 36 | * 开启 WebSocket 连接 37 | * */ 38 | public void connect() { 39 | interruptConsumer.accept(websocketExecutor); 40 | final BotConfiguration.ConnectionConfig connection = BotConfiguration.connection; 41 | websocketExecutor.execute(new WebSocketRunner(connection)); 42 | BotScheduler.runTaskLater(() -> { 43 | OpenAPIHandling.setClientName(connection.name); 44 | StaticAPI.getClient().setClientName(connection.name); 45 | }, 3L); 46 | } 47 | 48 | public void checkConnection() { 49 | if (OneBotConnection.isRunning()) { 50 | logger.info("账号连接成功"); 51 | } else { 52 | logger.warning("账号连接失败,请检查控制台输出处理,或等待自动重连"); 53 | } 54 | } 55 | 56 | public void close() { 57 | try { 58 | websocketExecutor.shutdown(); 59 | logger.info("WebSocket 连接关闭 " + (websocketExecutor.isShutdown() ? "成功" : "失败")); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | { 66 | Consumer consumer; 67 | try { 68 | final Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); 69 | field.setAccessible(true); 70 | MethodHandles.Lookup IMPL_LOOKUP = (MethodHandles.Lookup) field.get(null); 71 | final MethodHandle methodHandle = IMPL_LOOKUP 72 | .findVirtual(ThreadPoolExecutor.class, "interruptWorkers", MethodType.methodType(void.class)); 73 | consumer = object -> { 74 | try { 75 | methodHandle.invoke(object); 76 | } catch (Throwable e) { 77 | e.printStackTrace(); 78 | } 79 | }; 80 | } catch (Exception e) { 81 | consumer = object -> {}; // do nothing 82 | e.printStackTrace(); 83 | } 84 | this.interruptConsumer = consumer; 85 | } 86 | 87 | private class WebSocketRunner implements Runnable { 88 | 89 | private final BotConfiguration.ConnectionConfig connection; 90 | 91 | private WebSocketRunner(BotConfiguration.ConnectionConfig connection) { 92 | this.connection = connection; 93 | } 94 | 95 | @Override 96 | public void run() { 97 | try { 98 | OneBotConnection.start( 99 | connection.host, 100 | connection.port, 101 | connection.authorization, 102 | eventConsumer 103 | ); 104 | } catch (InterruptedException ignore) { 105 | // do nothing 106 | } catch (Exception e) { 107 | e.printStackTrace(); 108 | } 109 | logger.info("WebSocket 连接已关闭"); 110 | } 111 | 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/config/CommentConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration.config; 2 | 3 | 4 | import org.bukkit.configuration.InvalidConfigurationException; 5 | import org.bukkit.configuration.file.YamlConfiguration; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | public class CommentConfiguration extends YamlConfiguration { 14 | 15 | protected static String newLine = "\n"; 16 | // 新增保留注释字段 17 | protected static String commentPrefixSymbol = "'注释 "; 18 | protected static String commentSuffixSymbol = "': 注释"; 19 | protected static String fromRegex = "( *)(#.*)"; 20 | protected static Pattern fromPattern = Pattern.compile(fromRegex); 21 | protected static String toRegex = "( *)(- )*" + "(" + commentPrefixSymbol + ")" + "(#.*)" + "(" + commentSuffixSymbol + ")"; 22 | protected static Pattern toPattern = Pattern.compile(toRegex); 23 | protected static Pattern countSpacePattern = Pattern.compile("( *)(- )*(.*)"); 24 | protected static int commentSplitWidth = 90; 25 | 26 | @Override 27 | public void loadFromString(String contents) throws InvalidConfigurationException { 28 | String[] parts = contents.split(newLine); 29 | List lastComments = new ArrayList<>(); 30 | StringBuilder builder = new StringBuilder(); 31 | for (String part : parts) { 32 | Matcher matcher = fromPattern.matcher(part); 33 | if (matcher.find() && part.trim().startsWith("#")) { 34 | String originComment = matcher.group(2); 35 | String[] splitComments = split(originComment, commentSplitWidth); 36 | for (int i = 0; i < splitComments.length; ++ i) { 37 | String comment = splitComments[i]; 38 | if (i == 0) { 39 | comment = comment.substring(1); 40 | } 41 | comment = COMMENT_PREFIX + comment; 42 | lastComments.add(comment.replaceAll("\\.", ".").replaceAll("'", "'").replaceAll(":", ":")); 43 | } 44 | } else { 45 | matcher = countSpacePattern.matcher(part); 46 | if (matcher.find() && !lastComments.isEmpty()) { 47 | for (String comment : lastComments) { 48 | builder.append(matcher.group(1)); 49 | builder.append(checkNull(matcher.group(2))); 50 | builder.append(commentPrefixSymbol); 51 | builder.append(comment); 52 | builder.append(commentSuffixSymbol); 53 | builder.append(newLine); 54 | } 55 | lastComments.clear(); 56 | } 57 | builder.append(part); 58 | builder.append(newLine); 59 | } 60 | } 61 | super.loadFromString(builder.toString()); 62 | } 63 | 64 | @NotNull 65 | @Override 66 | public String saveToString() { 67 | String contents = super.saveToString(); 68 | StringBuilder saveContent = new StringBuilder(); 69 | String[] parts = contents.split(newLine); 70 | for (String part : parts) { 71 | Matcher matcher = toPattern.matcher(part); 72 | if (matcher.find() && matcher.groupCount() == 5) { 73 | part = checkNull(matcher.group(1)) + matcher.group(4); 74 | } 75 | saveContent.append(part.replaceAll(".", ".").replaceAll("'", "'").replaceAll(":", ":")); 76 | saveContent.append(newLine); 77 | } 78 | return saveContent.toString(); 79 | } 80 | 81 | private static String[] split(String string, int partLength) { 82 | String[] array = new String[string.length() / partLength + 1]; 83 | for (int i = 0; i < array.length; i++) { 84 | int beginIndex = i * partLength; 85 | int endIndex = beginIndex + partLength; 86 | if (endIndex > string.length()) { 87 | endIndex = string.length(); 88 | } 89 | array[i] = string.substring(beginIndex, endIndex); 90 | } 91 | return array; 92 | } 93 | 94 | private static String checkNull(String string) { 95 | return string == null ? "" : string; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/ExpansionConfig.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.infinite.bot.minecraft.configuration.config.CommentConfiguration; 5 | import com.illtamer.perpetua.sdk.util.Assert; 6 | import org.bukkit.configuration.InvalidConfigurationException; 7 | import org.bukkit.configuration.MemorySection; 8 | import org.bukkit.configuration.file.FileConfiguration; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.util.Set; 15 | 16 | public class ExpansionConfig { 17 | private final String fileName; 18 | private final IExpansion expansion; 19 | private final Integer version; 20 | private File file; 21 | private volatile FileConfiguration config; 22 | 23 | public ExpansionConfig(String fileName, IExpansion expansion) { 24 | this(fileName, expansion, 0); 25 | } 26 | 27 | public ExpansionConfig(String fileName, IExpansion expansion, int version) { 28 | this.expansion = expansion; 29 | this.fileName = fileName; 30 | Assert.isTrue(version >= 0, "Illegal version %d", version); 31 | this.version = version; 32 | this.config = load(); 33 | checkVersion(); 34 | } 35 | 36 | /** 37 | * 将内存中的数据保存到硬盘 38 | * */ 39 | public void save() { 40 | try { 41 | config.save(file); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | config = load(); 46 | } 47 | 48 | /** 49 | * 重新将硬盘的数据加载到内存中 50 | * */ 51 | public void reload() { 52 | config = load(); 53 | } 54 | 55 | /** 56 | * 获取文件对象 57 | * */ 58 | public FileConfiguration getConfig() { 59 | return this.config; 60 | } 61 | 62 | private FileConfiguration load() { 63 | File file = new File(expansion.getDataFolder(), fileName); 64 | if (!file.exists()) { 65 | expansion.saveResource(fileName, false); 66 | } 67 | CommentConfiguration yaml = new CommentConfiguration(); 68 | try { 69 | yaml.load(file); 70 | this.file = file; 71 | } catch (IOException | InvalidConfigurationException e) { 72 | e.printStackTrace(); 73 | } 74 | return yaml; 75 | } 76 | 77 | private void checkVersion() { 78 | final int depVersion = config.getInt("version", 0); 79 | switch (this.version.compareTo(depVersion)) { 80 | case 0: break; 81 | case -1: { 82 | expansion.getLogger().error("Deprecated expansion config version! Please try latest release."); 83 | break; 84 | } 85 | case 1: { 86 | backAndGenerateConfig(depVersion); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | private void backAndGenerateConfig(int depVersion) { 93 | File backFile = new File(expansion.getDataFolder(), String.format("%s-%d-%d.bak", fileName, depVersion, System.currentTimeMillis())); 94 | Assert.isTrue(file.renameTo(backFile), "Can't rename file '%s'", file.getName()); 95 | file = new File(expansion.getDataFolder(), fileName); 96 | try ( 97 | InputStream input = expansion.getResource(fileName); 98 | FileOutputStream output = new FileOutputStream(file) 99 | ) { 100 | int read; 101 | byte[] bytes = new byte[1024]; 102 | while ((read = input.read(bytes)) != -1) { 103 | output.write(bytes, 0, read); 104 | } 105 | } catch (IOException e) { 106 | expansion.getLogger().error("Read config failed.", e); 107 | return; 108 | } 109 | final Set oldConfigKeys = config.getKeys(true); 110 | CommentConfiguration newConfig = new CommentConfiguration(); 111 | try { 112 | newConfig.load(file); 113 | } catch (IOException | InvalidConfigurationException e) { 114 | expansion.getLogger().error("Config " + fileName + " load failed", e); 115 | } 116 | // add/delete -> continue; update -> set 117 | for (String key : newConfig.getKeys(true)) { 118 | if (key.length() == 7 && "version".equals(key)) continue; 119 | if (!oldConfigKeys.contains(key) || newConfig.get(key) instanceof MemorySection) continue; 120 | newConfig.set(key, config.get(key)); 121 | } 122 | config = newConfig; 123 | try { 124 | if (config.getInt("version") != version) { 125 | // 更新了代码版本,忘记更新配置版本号 126 | config.set("version", version); 127 | expansion.getLogger().warn("An deprecated version of embedded config was detected, the generate file was manually updated"); 128 | } 129 | config.save(file); 130 | } catch (IOException e) { 131 | e.printStackTrace(); 132 | } 133 | expansion.getLogger().info("Config(version: " + depVersion + ") auto-update to version-" + version + " now ! (The previous has been backed up)"); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /minecraft/src/test/resources/runtimeDownload.txt: -------------------------------------------------------------------------------- 1 | ===ALGORITHM SHA-256 2 | com.illtamer.perpetua.sdk:perpetua-sdk:0.1.0 e145f33a0972a993fac747dc71ec7b306e0b8ef334a22fff8ed5851e7fc1f6e7 3 | io.netty:netty-all:4.1.90.Final a3bf4c00eb67bac78d2cbd8ceb8fcf60899e6253dd84b54b2babc4c43f395b85 4 | io.netty:netty-transport-native-epoll:4.1.90.Final:linux-aarch_64 069161a6b7a792ee1804ac54ea921d8a45d7aef46dbdc5986d9197b0747577e3 5 | io.netty:netty-transport-classes-epoll:4.1.90.Final 43a8572ef3061cfb9dbbc7178dc3405b3fbc8562e6768aa92d9222d284bf35f7 6 | io.netty:netty-transport-native-unix-common:4.1.90.Final b5c2b82e8d4d46c5ad1015fd2bbb76513c5f88b558d6cc80d99e8a46ec14cca3 7 | io.netty:netty-transport:4.1.90.Final 252239c58c97f3e977205ac8eaa0caaa4d027848d308540dab02eb4e94727a78 8 | io.netty:netty-buffer:4.1.90.Final 2c6ff117eea297afd1989864bfd5e5ab122bbd0ef1aee641b6c86005db4496fe 9 | io.netty:netty-common:4.1.90.Final c6d6adbb364a9d8732a7b855566561848ccf11464ba774913d8e146c8774f90e 10 | io.netty:netty-resolver:4.1.90.Final e0aecc6e150a6f4f1e44dd8961f96a5b973397f6bc90007a2edab0e00331bc37 11 | io.netty:netty-transport-native-kqueue:4.1.90.Final:osx-aarch_64 a0e196855f14e8ca0e8e93d009df6ed2601f7336d16d681ffa30a595fbb54954 12 | io.netty:netty-transport-classes-kqueue:4.1.90.Final c52f681e928e3d913efa272737a5e35f0a09596030dd76d4bbfd9999bb04ca4b 13 | io.netty:netty-resolver-dns-native-macos:4.1.90.Final:osx-aarch_64 f189011b01f4b1ecd4e03bf332d52a0ac24a98eaa1f9ff7c9422ddab67ff1d26 14 | io.netty:netty-resolver-dns-classes-macos:4.1.90.Final fa329eec1e104634d0be1d21b4ba69657a8aef9429607b3df16ba142dbfb7e16 15 | io.netty:netty-resolver-dns:4.1.90.Final 451feb1ab8b005b92f017938982237cb98781f2eb0632d21813f4d82ad8e9dfd 16 | io.netty:netty-handler:4.1.90.Final 3ddc2a7753d2813f1c6d045c058448a66dcdbc4dc319d9c7e479017bff77d601 17 | io.netty:netty-codec:4.1.90.Final 8963bae9fcc264859f2e8bf87c271bfdd6b1ff3a6b76391c6c1e43779788dc9f 18 | io.netty:netty-codec-dns:4.1.90.Final eb2020e37c89b5f2bab9fa4893a87fd65e33d62e3a4814020deef68ad4dfb1c4 19 | io.netty:netty-codec-haproxy:4.1.90.Final 7f0b6985f7b9de4b7b212c4adfb11557aa72a18cc8c4a0f6740ca36daa69d4f0 20 | io.netty:netty-codec-http:4.1.90.Final e13867d1b87fcb07fbd60183ecfb61f1a3638472fea9002c5d578ff1b947ca22 21 | io.netty:netty-codec-http2:4.1.90.Final 98034fc474cc2b78b4d5e7e15fb89034bed28342cbee4a04cfc79406535f0e9a 22 | io.netty:netty-codec-memcache:4.1.90.Final d10c082ae0dbd8f0be4ef9e57e8e7eda0abee63781724356bf95900f8d58c774 23 | io.netty:netty-codec-mqtt:4.1.90.Final 7801d7456d50868f560bea4b2f8694f7a63be3118ea1691fc91b692731aacafd 24 | io.netty:netty-codec-redis:4.1.90.Final df5aac3a73c69cf133ddde0a28633f1dfe40975d3a16ab03325ed392f4be2b3e 25 | io.netty:netty-codec-smtp:4.1.90.Final 93f8ad3466f84ccb3570c62b168ba3f1b2a9ee10686528bfeb13d2c83be6db57 26 | io.netty:netty-codec-socks:4.1.90.Final 2bf6055ddc53e5e3f09e69348422e2f48ca1c8566baf82f90a945896aaaed82d 27 | io.netty:netty-codec-stomp:4.1.90.Final eecf92159abfb688f9c37b2e35a1c71d93f883c259b754d608518d6bb38a869e 28 | io.netty:netty-codec-xml:4.1.90.Final 5a1cd2913f9efeb86d6dd322b94c47368796a52215cefd65183a01c86b1ba9aa 29 | io.netty:netty-handler-proxy:4.1.90.Final fc50d622a8731129a17eb807c41d5546e767486303e56fb44210171caccbac7d 30 | io.netty:netty-handler-ssl-ocsp:4.1.90.Final 54ed6c1d2dcd84b5424b3509880767975d8378dfbd7a126856c33168bcf15756 31 | io.netty:netty-transport-rxtx:4.1.90.Final d37482e7a47d085d6e11b26238a0a87bc684dc3ec9ad7bef664ff38b5e3ff510 32 | io.netty:netty-transport-sctp:4.1.90.Final 08586e9f081a2f0266c88d019d009db98b6ce6e567b6265818235a960fa0c0cf 33 | io.netty:netty-transport-udt:4.1.90.Final e89bcc076ac73eed0e86f6dfbb367baefb790ee66084435b0a9849587ad56bd1 34 | org.apache.httpcomponents:fluent-hc:4.5.14 6916043081fd7b0acb3e50a12a0e82187063d7a6dfed3d2fb2f3296b580ab0f8 35 | org.apache.httpcomponents:httpclient:4.5.14 c8bc7e1c51a6d4ce72f40d2ebbabf1c4b68bfe76e732104b04381b493478e9d6 36 | commons-logging:commons-logging:1.2 daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636 37 | org.apache.httpcomponents:httpcore:4.4.16 6c9b3dd142a09dc468e23ad39aad6f75a0f2b85125104469f026e52a474e464f 38 | commons-codec:commons-codec:1.11 e599d5318e97aa48f42136a2927e6dfa4e8881dff0e6c8e3109ddbbff51d7b7d 39 | com.google.code.gson:gson:2.10.1 4241c14a7727c34feea6507ec801318a3d4a90f070e4525681079fb94ee4c593 40 | com.google.guava:guava:33.0.0-jre f4d85c3e4d411694337cb873abea09b242b664bb013320be6105327c45991537 41 | com.google.guava:failureaccess:1.0.2 8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064 42 | com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99 43 | com.google.code.findbugs:jsr305:3.0.2 766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7 44 | org.checkerframework:checker-qual:3.41.0 2f9f245bf68e4259d610894f2406dc1f6363dc639302bd566e8272e4f4541172 45 | com.google.errorprone:error_prone_annotations:2.23.0 ec6f39f068b6ff9ac323c68e28b9299f8c0a80ca512dccb1d4a70f40ac3ec054 46 | com.google.j2objc:j2objc-annotations:2.8 f02a95fa1a5e95edb3ed859fd0fb7df709d121a35290eff8b74dce2ab7f4d6ed 47 | org.jetbrains:annotations:24.0.1 61666dbce7e42e6c85b43c04fcfb8293a21dcb55b3c80e869270ce42c01a6b35 48 | mysql:mysql-connector-java:8.0.29 d4e32d2a6026b5acc00300b73a86c28fb92681ae9629b21048ee67014c911db6 49 | com.google.protobuf:protobuf-java:3.19.4 e8f524c2ad5965aae31b0527bf9d4e3bc19b0dfba8c05aef114fccc7f057c94d 50 | org.slf4j:slf4j-api:1.7.32 3624f8474c1af46d75f98bc097d7864a323c81b3808aa43689a6e1c601c027be -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/configuration/config/BotConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.configuration.config; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.ConfigSection; 5 | import com.illtamer.infinite.bot.minecraft.repository.PlayerDataRepository; 6 | import com.illtamer.infinite.bot.minecraft.repository.impl.DatabasePlayerDataRepository; 7 | import com.illtamer.infinite.bot.minecraft.repository.impl.YamlPlayerDataRepository; 8 | import com.illtamer.perpetua.sdk.util.Assert; 9 | import lombok.Getter; 10 | import lombok.ToString; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import javax.sql.DataSource; 15 | import java.io.File; 16 | import java.sql.Connection; 17 | import java.sql.SQLException; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.Optional; 21 | 22 | public class BotConfiguration { 23 | 24 | public static final String EXPANSION_FOLDER_NAME = "expansions"; 25 | 26 | @Getter 27 | private static volatile BotConfiguration instance; 28 | 29 | public static MainConfig main; 30 | public static DatabaseConfig database; 31 | public static ConnectionConfig connection; 32 | 33 | @Getter 34 | private final ConfigFile configFile; 35 | @Getter 36 | private final PlayerDataRepository repository; 37 | 38 | private BotConfiguration(Bootstrap instance) { 39 | this.configFile = new ConfigFile("config.yml", instance); 40 | loadConfigurations(); 41 | new File(instance.getDataFolder(), EXPANSION_FOLDER_NAME).mkdirs(); 42 | boolean localData = "yaml".equalsIgnoreCase(database.type); 43 | DataSource dataSource = null; 44 | if (!localData) { 45 | dataSource = new DataSourceConfiguration().getDataSource(); 46 | try (Connection connection = dataSource.getConnection()) { 47 | ; // do nothing 48 | } catch (SQLException ignore) { 49 | localData = true; 50 | instance.getLogger().warning("数据库连接异常,自动启用本地数据,如需使用数据库请修改后重新载入插件"); 51 | } 52 | } 53 | repository = localData ? 54 | new YamlPlayerDataRepository(new ConfigFile("player_data.yml", instance), instance) : 55 | new DatabasePlayerDataRepository(dataSource, instance); 56 | } 57 | 58 | /** 59 | * 从字符串加载配置文件 60 | * */ 61 | public void loadConfigurations(String yaml) { 62 | this.configFile.update(yaml); 63 | loadConfigurations(); 64 | } 65 | 66 | /** 67 | * 加载配置文件 68 | * */ 69 | private void loadConfigurations() { 70 | main = new MainConfig(); 71 | database = new DatabaseConfig(); 72 | connection = new ConnectionConfig(); 73 | } 74 | 75 | /** 76 | * 加载配置类 77 | * @return Singleton instance 78 | * */ 79 | public static BotConfiguration load(Bootstrap plugin) { 80 | Assert.isNull(instance, "Repeated initialization"); 81 | synchronized (BotConfiguration.class) { 82 | Assert.isNull(instance, "Repeated initialization"); 83 | instance = new BotConfiguration(plugin); 84 | } 85 | return instance; 86 | } 87 | 88 | /** 89 | * 重载配置类 90 | * @apiNote 仅重载 config.yml 文件内容,其它组件不提供重载支持 91 | * */ 92 | public static void reload() { 93 | instance.configFile.reload(); 94 | instance.loadConfigurations(); 95 | } 96 | 97 | /** 98 | * 保存缓存中改动的数据 99 | * */ 100 | public static void saveAndClose() { 101 | instance.repository.saveCacheData(); 102 | } 103 | 104 | @ToString 105 | @SuppressWarnings("all") 106 | public class MainConfig { 107 | 108 | private ConfigSection section = configFile.getConfig().getSection("main"); 109 | 110 | @NotNull 111 | public final List admins = section.getLongList("admins"); 112 | 113 | @NotNull 114 | public final List groups = section.getLongList("groups"); 115 | 116 | } 117 | 118 | @ToString 119 | @SuppressWarnings("all") 120 | public class DatabaseConfig { 121 | 122 | private ConfigSection section = configFile.getConfig().getSection("database"); 123 | 124 | @NotNull 125 | public final String type = section.getString("type", "yml"); 126 | 127 | @NotNull 128 | public final Map mysqlConfig = Optional.ofNullable(configFile.getConfig().getSection("database.config.mysql")).orElseThrow(() -> new NullPointerException("Nonexistent path: database.config.mysql")).getValues(false); 129 | 130 | } 131 | 132 | @ToString 133 | @SuppressWarnings("all") 134 | public class ConnectionConfig { 135 | 136 | private ConfigSection section = configFile.getConfig().getSection("connection"); 137 | 138 | @NotNull 139 | public final String name = section.getString("name", ""); 140 | 141 | @NotNull 142 | public final String host = section.getString("webapi.host", "unknown"); 143 | 144 | public final int port = section.getInt("webapi.port", -1); 145 | 146 | @Nullable 147 | public final String authorization = section.getString("authorization", null); 148 | 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /docs/zh-cn/dev-minecraft.md: -------------------------------------------------------------------------------- 1 | # Plugin - Expansion 2 | 3 | ## 导入 4 | 5 | 在开发 `InfiniteBot3-minecraft` 之前,您需要向您的项目中引入插件的坐标(或直接 [[下载插件]](https://github.com/IllTamer/infinitebot3/releases) 并导入,不推荐)。 6 | 7 | > 在 `minecraft` 模块中已完整包含 `api` 模块,所有在上文介绍过的 API 均被允许使用。 8 | 9 | ### Maven 10 | 11 | ```xml 12 | 13 | com.illtamer.infinite.bot 14 | minecraft 15 | 3.1.0 16 | 17 | ``` 18 | 19 | ## 编写附属 20 | 21 | !> 出于可用性、简洁性、不重复造轮子等方面考虑,本人强烈建议您以下方的附属开发规范为参考编写插件拓展,以快速开发代替传统附属插件编写方式——再写一个Bukkit插件(如果你喜欢的话)。 22 | 23 | ### 前言 24 | 25 | InfiniteBot-v3.+较一代完善了附属开发方面的不足,较二代补足了多服间数据互通的分布式需求。插件的附属拓展系统借鉴了Buukit-Plugin模式,由插件主体提供可热加载、具有完善监听、独特API的附属开发模式。 26 | 27 | > 即:附属除无需配置 plugin.yml 外生命周期相关 API 向 bukkit-plugin 规范靠齐。 28 | 29 | ### 附属主类注册 30 | 31 | #### Standard 32 | 33 | 附属主类需继承抽象类 `InfiniteExpansion` 并重写方法 34 | 35 | - `onEnable` 附属加载时调用 36 | - `onDisable` 附属卸载时调用 37 | - `getExpansionName` 用于注册附属,不为空! 38 | - `getVersion` 用于注册附属,不为空! 39 | - `getAuthor` 用于注册附属,不为空! 40 | 41 | 其他父类自带方法详见 [[IExpansion]](../src/main/java/com/illtamer/infinite/bot/minecraft/api/IExpansion.java) 42 | 43 | > `expansionName`, `version` 与 `author` 共同组成了附属唯一性标识 `expansionName-version::author`。\ 44 | > 一个唯一性标识最多允许注册一个 45 | 46 | 示例代码 47 | 48 | ```java 49 | public class ExampleExpansion extends InfiniteExpansion { 50 | @Override 51 | public void onEnable() { 52 | //TODO 53 | } 54 | @Override 55 | public void onDisable() { 56 | //TODO 57 | } 58 | @Override 59 | @NotNull 60 | public String getExpansionName() { 61 | return "ExampleExpansion"; 62 | } 63 | @Override 64 | @NotNull 65 | public String getVersion() { 66 | return "1.0"; 67 | } 68 | @Override 69 | @NotNull 70 | public String getAuthor() { 71 | return "IllTamer"; 72 | } 73 | } 74 | ``` 75 | 76 | #### With a Plugin 77 | 78 | 若您希望您的插件能与 `InfiniteBot3-minecraft` 挂钩(hook),可以按以下形式在您的插件中新建一个附属类并将其注册为外部附属(External Expansion) 79 | 80 | 编写附属类 81 | 82 | ```java 83 | public class ExternalExpansion extends AbstractExternalExpansion { 84 | @Override 85 | public void onEnable() { 86 | // TODO 87 | } 88 | @Override 89 | public void onDisable() { 90 | // TODO 91 | } 92 | @Override 93 | public String getExpansionName() { 94 | return "ExternalExpansion"; 95 | } 96 | @Override 97 | public String getVersion() { 98 | return "1.0-SNAPSHOT"; 99 | } 100 | @Override 101 | public String getAuthor() { 102 | return "IllTamer"; 103 | } 104 | } 105 | ``` 106 | 107 | 注册外部附属 108 | 109 | ```java 110 | public class TestPlugin extends JavaPlugin { 111 | @Override 112 | public void onEnable() { 113 | new ExternalExpansion().register(this); 114 | } 115 | } 116 | ``` 117 | 118 | ### 附属配置文件注册 119 | 120 | IB3已预先为您封装好了配置文件实体类 `ExpansionConfig`,您的配置文件应在附属初始化时被注册。当插件加载附属配置文件时,会从附属jar中寻找对应URL,若找到加载到缓存并自动生成到 `plugins/InfiniteBot3/expansions/附属名称` 下 示例代码 121 | 122 | ```java 123 | public class ExampleExpansion extends InfiniteExpansion { 124 | private IExpansion instance; 125 | private ExpansionConfig configFile; 126 | @Override 127 | public void onEnable() { 128 | instance = this; 129 | configFile = new ExpansionConfig("config.yml", instance); 130 | // ... 131 | } 132 | } 133 | ``` 134 | 135 | ExpansionConfig 已封装常用方法保存/重载/获取yml文件,详见 [[ExpansionConfig]](../src/main/java/com/illtamer/infinite/bot/minecraft/expansion/ExpansionConfig.java) 136 | 137 | ### 语言文件 138 | 139 | 语言文件基于 ExpansionConfig 二次封装实现,其固定格式为 `{name}-{type}.yml` (如 `language-zh_CN.yml`)。 140 | 语言文件相关 API 详见类 [[Language]](../src/main/java/com/illtamer/infinite/bot/minecraft/expansion/Language.java),且在不断完善中。 141 | 142 | ### QQ事件监听+注册 143 | 144 | > 注:该示例中所用到的类全位于 `com.illtamer.infinite.bot.api.event` 包下(包括Bukkit的同名类) 145 | 您的监听类应实现`Listener`接口,监听的方法应标有`@EventHandler`注解,方法中事件参数选择`InfiniteBot3`事件 146 | 147 | - 监听部分示例代码 148 | 149 | ```java 150 | public class ExampleListener implements Listener { 151 | // 可选优先级设置项 property 优先级高的方法将被优先调用 152 | @EventHandler 153 | public void onEvent(MessageEvent event) { 154 | // 引用发送者的消息发送一条内容为“收到”的消息 155 | event.reply("收到"); 156 | // 为保证注入消息转发类的插件能够正常工作,当您在执行例如监听关键字的特定回复操作后,请务必取消时间避免消息被转发 157 | event.setCancelled(true); 158 | } 159 | } 160 | ``` 161 | 162 | - 注册监听部分示例代码 163 | 164 | > 附属注册的所有事件均会在附属卸载后自动被注销 165 | 166 | ```java 167 | public class ExampleExpansion extends InfiniteExpansion { 168 | private IExpansion instance; 169 | @Override 170 | public void onEnable() { 171 | instance = this; 172 | // 注册 IB3 事件 173 | EventExecutor.registerListener(new ExampleListener(), instance); 174 | // 注册 Bukkit 事件 175 | EventExecutor.registerBukkitListener(new BukkitListener()); 176 | // ... 177 | } 178 | } 179 | ``` 180 | 181 | ## API 182 | 183 | 插件本体中内含两种 API 184 | 185 | 1. 模块 API 186 | 187 | 见 [开发相关 - API](dev-api.md) 188 | 189 | 2. 插件 API 190 | 191 | 作者所能确保的不会删减的 API 在 `com.illtamer.infinite.bot.minecraft.api` 包内,其中 [[StaticAPI]](../src/main/java/com/illtamer/infinite/bot/minecraft/api/StaticAPI.java) 提供部分 API 的便捷静态方法调用。 192 | 193 | 插件的启动类为 `com.illtamer.infinite.bot.minecraft.start.Bootstrap`,如有需要,您可获取启动类实例以获取附属加载类 [[ExpansionLoader]](../src/main/java/com/illtamer/infinite/bot/minecraft/expansion/ExpansionLoader.java) 的实例,对插件的附属进行修改操作。 194 | -------------------------------------------------------------------------------- /minecraft/src/test/java/com/illtamer/infinite/bot/minecraft/BootstrapTests.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler; 4 | import com.illtamer.infinite.bot.minecraft.configuration.config.CommentConfiguration; 5 | import com.illtamer.infinite.bot.minecraft.pojo.TimedBlockingCache; 6 | import com.illtamer.infinite.bot.minecraft.util.ProcessBar; 7 | import dev.vankka.dependencydownload.DependencyManager; 8 | import dev.vankka.dependencydownload.classloader.IsolatedClassLoader; 9 | import dev.vankka.dependencydownload.repository.Repository; 10 | import dev.vankka.dependencydownload.repository.StandardRepository; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.File; 14 | import java.io.FileInputStream; 15 | import java.io.InputStreamReader; 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.concurrent.CompletableFuture; 21 | import java.util.concurrent.ExecutorService; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.stream.Collectors; 25 | 26 | public class BootstrapTests { 27 | 28 | 29 | // public static void main(String[] args) { 30 | //// CQHttpWebSocketConfiguration.setHttpUri("http://47.117.136.149:5700"); 31 | //// CQHttpWebSocketConfiguration.setAuthorization("root765743073"); 32 | //// System.out.println(OpenAPIHandling.getMessage(1051692076)); 33 | // 34 | // } 35 | 36 | public static void main(String[] args) throws Exception { 37 | // processBar(); 38 | // testLibsLoader(); 39 | testTimedBlockingCache(); 40 | // DependencyManager manager = new DependencyManager(new File("cache").toPath()); 41 | // manager.addDependency(new StandardDependency( 42 | // "com.illtamer.infinite.bot", 43 | // "api", 44 | // "1.0.13", 45 | // null, 46 | // "081904f730f0a03d5959bc764b8d196b4d96a041cd66d026b40ed3ae41d5721e", 47 | // "SHA-256" 48 | // )); 49 | // Executor executor = Executors.newCachedThreadPool(); 50 | 51 | // All of these return CompletableFuture it is important to let the previous step finishing before starting the next 52 | // manager.downloadAll(executor, Collections.singletonList(new StandardRepository("https://repo.maven.apache.org/maven2"))).join(); 53 | // manager.loadAll(executor, new IsolatedClassLoader()).join(); 54 | } 55 | 56 | private static void testCommentConfig() throws Exception { 57 | // CommentConfiguration config = new CommentConfiguration(); 58 | // config.loadFromString("point1: '123456'\n" + 59 | // "part:\n" + 60 | // " #666666\n" + 61 | // " point2: '#123456'"); 62 | // System.out.println(config.saveToString()); 63 | } 64 | 65 | private static void testTimedBlockingCache() { 66 | try { 67 | TimedBlockingCache cache = new TimedBlockingCache<>(20); 68 | new Thread(() -> { 69 | try { 70 | Thread.sleep(3000); 71 | cache.put("key", "value"); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } 75 | }).start(); 76 | Object value = cache.get("key", 10, TimeUnit.SECONDS); 77 | System.out.println("get data after 3s: " + value); 78 | } catch (Exception e) { 79 | e.printStackTrace(); 80 | } 81 | } 82 | 83 | private static void testLibsLoader() throws Exception { 84 | List REPOSITORIES = Arrays.asList( 85 | new StandardRepository("https://repo.maven.apache.org/maven2"), 86 | new StandardRepository("https://oss.sonatype.org/content/repositories/snapshots"), 87 | new StandardRepository("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") 88 | ); 89 | 90 | File dependencyFolder = new File("/home/illtamer/Code/java/idea/github/infinitebot4/minecraft/src/test/resources/libs"); 91 | if (!dependencyFolder.exists()) dependencyFolder.mkdirs(); 92 | ExecutorService executor = Executors.newFixedThreadPool(32); 93 | 94 | DependencyManager manager = new DependencyManager(dependencyFolder.toPath()); 95 | String resource = new BufferedReader(new InputStreamReader(Files.newInputStream(Paths.get("/home/illtamer/Code/java/idea/github/infinitebot4/minecraft/src/test/resources/runtimeDownload.txt")))) 96 | .lines().collect(Collectors.joining(System.lineSeparator())); 97 | 98 | manager.loadFromResource(resource); 99 | System.out.println("start download"); 100 | manager.downloadAll(executor, REPOSITORIES).join(); 101 | System.out.println("download done"); 102 | // manager.relocateAll(executor).join(); 103 | 104 | CompletableFuture[] tasks = manager.load(executor, new IsolatedClassLoader()); 105 | for (int i = 0; i < tasks.length; i++) { 106 | tasks[i].join(); 107 | System.out.println(String.format("[Done] %d/%d", i+1, tasks.length)); 108 | } 109 | System.out.println("Finish"); 110 | } 111 | 112 | private static void processBar() throws Exception { 113 | int total = 10; 114 | ProcessBar b = ProcessBar.create(total); 115 | for (int i = 0; i < total; i++) { 116 | b.count(); 117 | Thread.sleep(1000); 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/repository/impl/YamlPlayerDataRepository.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.repository.impl; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 4 | import com.illtamer.infinite.bot.minecraft.api.adapter.ConfigSection; 5 | import com.illtamer.infinite.bot.minecraft.api.adapter.Configuration; 6 | import com.illtamer.infinite.bot.minecraft.configuration.config.ConfigFile; 7 | import com.illtamer.infinite.bot.minecraft.pojo.PlayerData; 8 | import com.illtamer.infinite.bot.minecraft.repository.PlayerDataRepository; 9 | import com.illtamer.infinite.bot.minecraft.util.Lambda; 10 | import com.illtamer.perpetua.sdk.util.Assert; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.Set; 17 | import java.util.UUID; 18 | import java.util.logging.Logger; 19 | import java.util.stream.Collectors; 20 | 21 | public class YamlPlayerDataRepository implements PlayerDataRepository { 22 | 23 | private final Logger log; 24 | private final ConfigFile dataFile; 25 | private final Map playerDataMap; 26 | 27 | public YamlPlayerDataRepository(ConfigFile dataFile, Bootstrap instance) { 28 | this.log = instance.getLogger(); 29 | this.dataFile = dataFile; 30 | final Configuration config = dataFile.getConfig(); 31 | Set idSet = config.getKeys(false); 32 | this.playerDataMap = idSet.stream().collect(Collectors.toMap(id -> id, id -> { 33 | PlayerData playerData = new PlayerData(); 34 | final ConfigSection section = config.getSection(id); 35 | if (section == null) return playerData; 36 | playerData.setUuid(section.getString("uuid", null)); 37 | playerData.setValidUUID(section.getString("valid_uuid", null)); 38 | playerData.setUserId(section.getLong("user_id", 0L)); 39 | playerData.setPermission(section.getStringList("permission")); 40 | return playerData; 41 | })); 42 | } 43 | 44 | @Override 45 | public boolean save(@NotNull PlayerData data) { 46 | Map.Entry dataEntry = getEntryByUUIDorUserId(data); 47 | if (dataEntry != null) { 48 | log.warning(String.format("PlayerData(%s) is already exists!", dataEntry.getValue())); 49 | return false; 50 | } 51 | String id; 52 | do { 53 | id = UUID.randomUUID().toString(); 54 | } while (playerDataMap.containsKey(id)); 55 | playerDataMap.put(id, data); 56 | return true; 57 | } 58 | 59 | @Override 60 | public PlayerData queryByUUID(@NotNull UUID uuid) { 61 | return queryByUUID(uuid.toString()); 62 | } 63 | 64 | @Override 65 | public @Nullable PlayerData queryByUUID(@NotNull String uuid) { 66 | return Lambda.nullableInvoke(Map.Entry::getValue, doQueryByUUID(uuid)); 67 | } 68 | 69 | @Override 70 | public @Nullable PlayerData queryByUserId(@NotNull Long userId) { 71 | return Lambda.nullableInvoke(Map.Entry::getValue, doQueryByUserId(userId)); 72 | } 73 | 74 | @Override 75 | @Nullable 76 | public PlayerData update(@NotNull PlayerData data) { 77 | Map.Entry dataEntry = getEntryByUUIDorUserId(data); 78 | if (dataEntry == null) { 79 | log.warning(String.format("PlayerData(%s) can not be found!", data)); 80 | return null; 81 | } 82 | final String key = dataEntry.getKey(); 83 | playerDataMap.put(key, data); 84 | return dataEntry.getValue(); 85 | } 86 | 87 | @Override 88 | @Nullable 89 | public PlayerData delete(@NotNull String uuid) { 90 | final Map.Entry dataEntry = doQueryByUUID(uuid); 91 | if (dataEntry == null) return null; 92 | return playerDataMap.remove(dataEntry.getKey()); 93 | } 94 | 95 | @Override 96 | @Nullable 97 | public PlayerData delete(@NotNull Long userId) { 98 | final Map.Entry dataEntry = doQueryByUserId(userId); 99 | if (dataEntry == null) return null; 100 | return playerDataMap.remove(dataEntry.getKey()); 101 | } 102 | 103 | @Override 104 | public void saveCacheData() { 105 | final Configuration config = dataFile.getConfig(); 106 | playerDataMap.forEach((key, value) -> { 107 | ConfigSection section = config.getSection(key); 108 | if (section == null) 109 | section = config.createSection(key, value.toMap()); 110 | else { 111 | section.set("uuid", value.getUuid()); 112 | section.set("valid_uuid", value.getValidUUID()); 113 | section.set("user_id", value.getUserId()); 114 | section.set("permission", value.getPermission()); 115 | } 116 | }); 117 | dataFile.save(); 118 | } 119 | 120 | @Nullable 121 | protected Map.Entry getEntryByUUIDorUserId(PlayerData data) { 122 | Map.Entry dataEntry = null; 123 | final String uuid = data.getUuid() == null ? data.getValidUUID() : data.getUuid(); 124 | if (uuid != null) { 125 | dataEntry = doQueryByUUID(uuid); 126 | } 127 | if (dataEntry == null) { 128 | Long userId = data.getUserId(); 129 | Assert.notNull(userId, "At least one of 'uuid' and 'userId' can not be null"); 130 | dataEntry = doQueryByUserId(userId); 131 | } 132 | return dataEntry; 133 | } 134 | 135 | @Nullable 136 | private Map.Entry doQueryByUUID(@NotNull String uuid) { 137 | Optional> first = playerDataMap.entrySet().stream() 138 | .filter(entry -> { 139 | final PlayerData data = entry.getValue(); 140 | if (data == null) return false; 141 | return uuid.equals(data.getUuid()) || uuid.equals(data.getValidUUID()); 142 | }).findFirst(); 143 | return first.orElse(null); 144 | } 145 | 146 | @Nullable 147 | private Map.Entry doQueryByUserId(long userId) { 148 | Optional> first = playerDataMap.entrySet().stream() 149 | .filter(entry -> { 150 | final PlayerData data = entry.getValue(); 151 | return data != null && data.getUserId().equals(userId); 152 | }).findFirst(); 153 | return first.orElse(null); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/api/EventExecutor.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.api; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.event.EventHandler; 4 | import com.illtamer.infinite.bot.minecraft.api.event.EventPriority; 5 | import com.illtamer.infinite.bot.minecraft.api.event.Listener; 6 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 7 | import com.illtamer.perpetua.sdk.entity.transfer.entity.Client; 8 | import com.illtamer.perpetua.sdk.event.Cancellable; 9 | import com.illtamer.perpetua.sdk.event.Event; 10 | import com.illtamer.perpetua.sdk.event.meta.HeartbeatEvent; 11 | import com.illtamer.perpetua.sdk.event.notice.ClientStatusChangeEvent; 12 | import com.illtamer.perpetua.sdk.util.ClassUtil; 13 | import org.bukkit.Bukkit; 14 | import org.bukkit.event.HandlerList; 15 | 16 | import java.lang.annotation.Annotation; 17 | import java.lang.reflect.Method; 18 | import java.util.*; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.logging.Logger; 21 | 22 | public class EventExecutor { 23 | 24 | /** 25 | * 经注册的 Listener 实现类对象 26 | * */ 27 | private static final HashMap> LISTENERS = new HashMap<>(); 28 | 29 | /** 30 | * 附属注册的 Bukkit Event 31 | * */ 32 | private static final Map> EXPANSION_BUKKIT_LISTENERS = new ConcurrentHashMap<>(); 33 | 34 | /** 35 | * 读取到的需要监听的方法 36 | * */ 37 | private static final HashMap> METHODS = new HashMap<>(); 38 | 39 | /** 40 | * 注册监听器 41 | * @param listener 实现Listener的监听对象 42 | * */ 43 | public static void registerEvents(Listener listener, IExpansion instance) { 44 | LISTENERS.computeIfAbsent(instance, 45 | k -> new HashSet<>(Collections.singleton(listener)) 46 | ).add(listener); 47 | // 加载注册方法 48 | final HashMap methods = ClassUtil.getMethods(listener, EventHandler.class); 49 | METHODS.put(listener, methods); 50 | } 51 | 52 | /** 53 | * 以 IB3 的名义向 Bukkit 注册附属中的 Bukkit Event Listener 54 | * @apiNote 注册的监听会在附属卸载时自动注销 55 | * */ 56 | public static void registerBukkitEvent(org.bukkit.event.Listener bukkitListener, IExpansion expansion) { 57 | if (EXPANSION_BUKKIT_LISTENERS.computeIfAbsent(expansion, k -> new HashSet<>()).add(bukkitListener)) { 58 | Bukkit.getPluginManager().registerEvents(bukkitListener, BukkitBootstrap.getInstance()); 59 | } else { 60 | BukkitBootstrap.getInstance().getLogger().warning(String.format("The listener(%s) is repeatedly registered by the expansion(%s)", bukkitListener, expansion)); 61 | } 62 | } 63 | 64 | /** 65 | * 注销附属注册的 Bukkit 监听 66 | * @apiNote 在插件卸载时自动调用 67 | * */ 68 | public static void unregisterBukkitEventForExpansion(IExpansion expansion) { 69 | final Set remove = EXPANSION_BUKKIT_LISTENERS.remove(expansion); 70 | if (remove != null) { 71 | remove.forEach(HandlerList::unregisterAll); 72 | } 73 | } 74 | 75 | /** 76 | * 注销附属的监听 77 | * @apiNote 如需注销附属,请使用 {@link com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader#disableExpansion(String)} 78 | * */ 79 | public static void unregisterListeners(IExpansion instance) { 80 | final Set listeners = LISTENERS.remove(instance); 81 | if (listeners == null) { 82 | return; 83 | } 84 | // 卸载注册方法 85 | synchronized (METHODS) { 86 | listeners.forEach(METHODS::remove); 87 | } 88 | } 89 | 90 | /** 91 | * 轮询执行事件 调度异步线程 92 | * */ 93 | public static void dispatchListener(Event event) { 94 | BotScheduler.runTask(new Dispatcher(event)); 95 | } 96 | 97 | /** 98 | * 事件调度类 99 | * */ 100 | private static class Dispatcher implements Runnable { 101 | 102 | private final Event event; 103 | 104 | public Dispatcher(Event event) { 105 | this.event = event; 106 | } 107 | 108 | @Override 109 | public void run() { 110 | if (event instanceof HeartbeatEvent) { 111 | return; 112 | } 113 | final Logger logger = BukkitBootstrap.getInstance().getLogger(); 114 | if (event instanceof ClientStatusChangeEvent) { 115 | ClientStatusChangeEvent e = (ClientStatusChangeEvent) event; 116 | if (e.getSelfClient()) { 117 | Client client = e.getClient(); 118 | StaticAPI.getClient().setAppId(client.getAppId()); 119 | logger.info(String.format("Perpetua connected with clientId: %s", client.getAppId())); 120 | } 121 | return; 122 | } 123 | for (Map.Entry> map : METHODS.entrySet()) { // class 124 | Object instance = map.getKey(); 125 | for (int i = EventPriority.values().length-1; i >= 0; i --) { 126 | EventPriority current = EventPriority.values()[i]; 127 | for (Map.Entry entry : map.getValue().entrySet()) { // method 128 | if (current != ((EventHandler) entry.getValue()).priority()) { 129 | continue; 130 | } 131 | if (event instanceof Cancellable && ((Cancellable) event).isCancelled()) { 132 | continue; 133 | } 134 | Method method = entry.getKey(); 135 | if (method.getParameterCount() != 1) { 136 | logger.warning(String.format("Excepted parameter count: %d(%s)", method.getParameterCount(), method)); 137 | continue; 138 | } 139 | Class paramType = method.getParameterTypes()[0]; 140 | if (!Event.class.isAssignableFrom(paramType)) { 141 | logger.warning(String.format("Unknown param type(%s) in %s", paramType, method)); 142 | } 143 | if (paramType.isInstance(event)) { 144 | try { 145 | method.invoke(instance, event); 146 | } catch (Exception e) { 147 | e.printStackTrace(); 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/listener/handler/CommandHelper.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.listener.handler; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.BotScheduler; 4 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 5 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion; 6 | import com.illtamer.infinite.bot.minecraft.api.StaticAPI; 7 | import com.illtamer.infinite.bot.minecraft.api.adapter.Bootstrap; 8 | import com.illtamer.infinite.bot.minecraft.api.adapter.CommandSender; 9 | import com.illtamer.infinite.bot.minecraft.configuration.StatusCheckRunner; 10 | import com.illtamer.infinite.bot.minecraft.configuration.config.BotConfiguration; 11 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionLoader; 12 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 13 | import com.illtamer.infinite.bot.minecraft.util.StringUtil; 14 | import com.illtamer.perpetua.sdk.entity.transfer.entity.Client; 15 | import com.illtamer.perpetua.sdk.entity.transfer.entity.LoginInfo; 16 | import com.illtamer.perpetua.sdk.entity.transfer.entity.Status; 17 | 18 | import java.io.File; 19 | import java.text.SimpleDateFormat; 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.Set; 24 | import java.util.stream.Collectors; 25 | 26 | public class CommandHelper { 27 | 28 | private static final SimpleDateFormat FORMAT = new SimpleDateFormat("HH:mm:ss"); 29 | 30 | public static boolean onCommand(CommandSender sender, String[] args, Bootstrap.Type type) { 31 | if (!sender.isOp()) return true; 32 | if (args.length == 1) { 33 | if ("debug".equals(args[0])) { 34 | System.out.println(BotConfiguration.connection); 35 | return true; 36 | } 37 | if ("help".equalsIgnoreCase(args[0])) { 38 | sender.sendMessage(new String[] { 39 | "ib3:", 40 | " ├──help: 获取帮助", 41 | " ├──check: 检查账号的连接状态", 42 | " ├──reload: 重载本体配置文件", 43 | " ├──expansions", 44 | " │ ├──list: 列出所有加载的附属名称", 45 | " │ └──reload: 重载 expansions 目录下所有附属", 46 | " ├──load", 47 | " │ └──[附属文件名]: 加载名称对应附属", 48 | " └──unload", 49 | " └──[附属名称]: 卸载名称对应附属" 50 | }); 51 | } else if ("check".equalsIgnoreCase(args[0])) { 52 | final long lastRefreshTime = StatusCheckRunner.getLastRefreshTime(); 53 | final LoginInfo loginInfo = StatusCheckRunner.getLoginInfo(); 54 | if (loginInfo == null) { 55 | sender.sendMessage("登陆数据刷新中,请稍后再试"); 56 | return true; 57 | } 58 | Client client = StaticAPI.getClient(); 59 | sender.sendMessage(String.format( 60 | "当前登陆账号: %d(%s)\nClient: %s(id: %s)\n上次更新: %s", 61 | loginInfo.getUserId(), loginInfo.getNickname(), 62 | StringUtil.isBlank(client.getClientName()) ? "[未设置别名]" : client.getClientName(), client.getAppId(), 63 | FORMAT.format(lastRefreshTime) 64 | )); 65 | } else if ("reload".equalsIgnoreCase(args[0])) { 66 | BotConfiguration.reload(); 67 | sender.sendMessage("config.yml 部分配置节点重载完成"); 68 | } 69 | return true; 70 | } 71 | if (args.length != 2) return true; 72 | if (type != Bootstrap.Type.BUKKIT) { 73 | sender.sendMessage("当前非 BUKKIT 环境,部分指令已禁用"); 74 | return true; 75 | } 76 | final ExpansionLoader loader = BukkitBootstrap.getInstance().getExpansionLoader(); 77 | if ("expansions".equalsIgnoreCase(args[0])) { 78 | if ("list".equalsIgnoreCase(args[1])) { 79 | final Set set = loader.getExpansionKeySet(); 80 | String[] keyArray = new String[set.size()]; 81 | sender.sendMessage(set.toArray(keyArray)); 82 | } else if ("reload".equalsIgnoreCase(args[1])) { 83 | BotScheduler.runTask(() -> { 84 | loader.disableExpansions(true); 85 | loader.loadExpansions(true); 86 | }); 87 | } 88 | } else if ("load".equalsIgnoreCase(args[0])) { 89 | File file = new File(loader.getPluginFolder(), args[1]); 90 | if (!file.exists() || file.isDirectory()) { 91 | sender.sendMessage("文件类型错误"); 92 | return true; 93 | } 94 | loader.loadExpansion(file); 95 | } else if ("unload".equalsIgnoreCase(args[0])) { 96 | final IExpansion expansion = loader.getExpansion(args[1]); 97 | if (expansion == null) { 98 | sender.sendMessage("附属标识符 " + args[1] + " 不存在"); 99 | return true; 100 | } 101 | if (expansion instanceof IExternalExpansion && ((IExternalExpansion) expansion).isPersist()) { 102 | sender.sendMessage("该附属为持久化附属,不可通过指令卸载!"); 103 | return true; 104 | } 105 | loader.disableExpansion(args[1]); 106 | } 107 | return true; 108 | } 109 | 110 | public static List onTabComplete(CommandSender sender, String[] args) { 111 | if (!sender.isOp()) return null; 112 | List result = new ArrayList<>(); 113 | if (args.length == 1) 114 | result.addAll(Arrays.asList("help", "check", "reload", "expansions", "load", "unload")); 115 | else if (args.length == 2) { 116 | switch (args[0]) { 117 | case "expansions": { 118 | result.add("list"); 119 | result.add("reload"); 120 | break; 121 | } 122 | case "unload": { 123 | final ExpansionLoader loader = BukkitBootstrap.getInstance().getExpansionLoader(); 124 | result.addAll(loader.getExpansionKeySet()); 125 | break; 126 | } 127 | case "load": { 128 | final ExpansionLoader loader = BukkitBootstrap.getInstance().getExpansionLoader(); 129 | File[] files = loader.getPluginFolder().listFiles((dir, name) -> name.endsWith(".jar")); 130 | if (files != null) { 131 | final List collect = Arrays.stream(files) 132 | .filter(file -> !file.isDirectory()) 133 | .map(File::getName) 134 | .collect(Collectors.toList()); 135 | result.addAll(collect); 136 | } 137 | break; 138 | } 139 | } 140 | } 141 | return result; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/manager/InfinitePluginLoader.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.manager; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.EventExecutor; 4 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 5 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion; 6 | import com.illtamer.infinite.bot.minecraft.exception.InvalidExpansionException; 7 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 8 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil; 9 | import com.illtamer.perpetua.sdk.util.Assert; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Set; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.CopyOnWriteArrayList; 18 | import java.util.logging.Logger; 19 | 20 | /** 21 | * 附属管理类 22 | * */ 23 | public class InfinitePluginLoader { 24 | private final Map> globalClasses = new ConcurrentHashMap<>(); 25 | private final List loaders = new CopyOnWriteArrayList<>(); 26 | 27 | /** 28 | * @param jarFile 加载一个已经过命名检查的 29 | * 存在的jarFile 30 | * */ 31 | public IExpansion loadExpansion(File jarFile) throws InvalidExpansionException { 32 | PluginClassLoader loader; 33 | try { 34 | loader = new PluginClassLoader(this, getClass().getClassLoader(), jarFile); 35 | loaders.add(loader); 36 | } catch (Exception e) { 37 | throw new InvalidExpansionException(e); 38 | } 39 | return loader.expansion; 40 | } 41 | 42 | /** 43 | * 开启附属 44 | * */ 45 | public void enableExpansion(IExpansion expansion) { 46 | Assert.isTrue(expansion instanceof InfiniteExpansion, "Expansion is not associated with this PluginLoader"); 47 | if (!expansion.isEnabled()) { 48 | final Logger logger = BukkitBootstrap.getInstance().getLogger(); 49 | logger.info(String.format("Enabling %s", ExpansionUtil.formatIdentifier(expansion))); 50 | InfiniteExpansion infiniteExpansion = (InfiniteExpansion) expansion; 51 | PluginClassLoader pluginLoader = (PluginClassLoader)infiniteExpansion.getClassLoader(); 52 | 53 | if (!this.loaders.contains(pluginLoader)) { 54 | this.loaders.add(pluginLoader); 55 | logger.warning("Enabled expansion " + ExpansionUtil.formatIdentifier(expansion) + " with unregistered PluginClassLoader"); 56 | } 57 | 58 | try { 59 | infiniteExpansion.setEnabled(true); 60 | } catch (Throwable ex) { 61 | logger.severe("Error occurred while enabling " + ExpansionUtil.formatIdentifier(expansion) + " (Is it up to date?)"); 62 | ex.printStackTrace(); 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * 开启外置附属 69 | * */ 70 | public void enableExternalExpansion(IExternalExpansion expansion) { 71 | Assert.isTrue(expansion instanceof AbstractExternalExpansion, "ExternalExpansion is not associated with this PluginLoader"); 72 | if (!expansion.isEnabled()) { 73 | final Logger logger = BukkitBootstrap.getInstance().getLogger(); 74 | logger.info(String.format("Enabling external-expansion %s", ExpansionUtil.formatIdentifier(expansion))); 75 | AbstractExternalExpansion externalExpansion = (AbstractExternalExpansion) expansion; 76 | 77 | try { 78 | externalExpansion.setEnabled(true); 79 | } catch (Throwable ex) { 80 | logger.severe("Error occurred while enabling " + ExpansionUtil.formatIdentifier(expansion) + " (Is it up to date?)"); 81 | ex.printStackTrace(); 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * 关闭附属 88 | * */ 89 | public void disableExpansion(IExpansion expansion) { 90 | Assert.isTrue(expansion instanceof InfiniteExpansion, "Expansion is not associated with this PluginLoader"); 91 | 92 | if (expansion.isEnabled()) { 93 | final Logger logger = BukkitBootstrap.getInstance().getLogger(); 94 | logger.info(String.format("Disabling expansion %s", ExpansionUtil.formatIdentifier(expansion))); 95 | // 注销监听 96 | EventExecutor.unregisterListeners(expansion); 97 | EventExecutor.unregisterBukkitEventForExpansion(expansion); 98 | 99 | InfiniteExpansion infiniteExpansion = (InfiniteExpansion) expansion; 100 | ClassLoader classLoader = infiniteExpansion.getClassLoader(); 101 | 102 | try { 103 | infiniteExpansion.setEnabled(false); 104 | } catch (Throwable ex) { 105 | logger.severe("Error occurred while disabling " + ExpansionUtil.formatIdentifier(infiniteExpansion) + " (Is it up to date?)"); 106 | ex.printStackTrace(); 107 | } 108 | 109 | if (classLoader instanceof PluginClassLoader) { 110 | PluginClassLoader loader = (PluginClassLoader) classLoader; 111 | this.loaders.remove(loader); 112 | Set names = loader.getClasses(); 113 | for (String name : names) { 114 | removeGlobalClasses(name); 115 | } 116 | try { 117 | loader.close(); 118 | } catch (IOException e) { 119 | e.printStackTrace(); 120 | } 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * 关闭外置附属 127 | * */ 128 | public void disableExternalExpansion(IExternalExpansion expansion) { 129 | Assert.isTrue(expansion instanceof AbstractExternalExpansion, "ExternalExpansion is not associated with this PluginLoader"); 130 | 131 | if (expansion.isEnabled()) { 132 | final Logger logger = BukkitBootstrap.getInstance().getLogger(); 133 | logger.info(String.format("Disabling external-expansion %s", ExpansionUtil.formatIdentifier(expansion))); 134 | // 注销监听 135 | EventExecutor.unregisterListeners(expansion); 136 | EventExecutor.unregisterBukkitEventForExpansion(expansion); 137 | 138 | AbstractExternalExpansion externalExpansion = (AbstractExternalExpansion) expansion; 139 | 140 | try { 141 | externalExpansion.setEnabled(false); 142 | } catch (Throwable ex) { 143 | logger.severe("Error occurred while disabling " + ExpansionUtil.formatIdentifier(externalExpansion) + " (Is it up to date?)"); 144 | ex.printStackTrace(); 145 | } 146 | } 147 | } 148 | 149 | void setGlobalClasses(String name, Class clazz) { 150 | if (!globalClasses.containsKey(name)) { 151 | globalClasses.put(name, clazz); 152 | } 153 | } 154 | 155 | Class getGlobalClassByName(String name) { 156 | Class cachedClass = globalClasses.get(name); 157 | if (cachedClass != null) { 158 | return cachedClass; 159 | } 160 | for (PluginClassLoader loader : this.loaders) { 161 | try { 162 | cachedClass = loader.findClass(name, false); 163 | } catch (ClassNotFoundException ignore) {} 164 | if (cachedClass != null) { 165 | return cachedClass; 166 | } 167 | } 168 | return null; 169 | } 170 | 171 | /** 172 | * @see #disableExpansion(IExpansion) 173 | * */ 174 | private void removeGlobalClasses(String name) { 175 | globalClasses.remove(name); 176 | } 177 | 178 | public Map> getGlobalClasses() { 179 | return globalClasses; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/automation/AutoLoadConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.automation; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.infinite.bot.minecraft.expansion.ExpansionConfig; 5 | import com.illtamer.infinite.bot.minecraft.expansion.automation.annotation.ConfigClass; 6 | import com.illtamer.infinite.bot.minecraft.expansion.automation.annotation.ConfigField; 7 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 8 | import com.illtamer.infinite.bot.minecraft.util.AutoConfigUtil; 9 | import com.illtamer.perpetua.sdk.util.Assert; 10 | import lombok.SneakyThrows; 11 | import org.bukkit.configuration.MemorySection; 12 | import org.bukkit.configuration.file.FileConfiguration; 13 | import org.bukkit.configuration.serialization.ConfigurationSerializable; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.lang.reflect.Constructor; 17 | import java.lang.reflect.Field; 18 | import java.lang.reflect.Method; 19 | import java.lang.reflect.Modifier; 20 | import java.util.*; 21 | 22 | /** 23 | * 自动配置类 24 | * @see #doDeserialize(Map, Class, IExpansion) 25 | * @see Registration 26 | * */ 27 | public abstract class AutoLoadConfiguration implements ConfigurationSerializable { 28 | 29 | // default config filed 30 | protected int version; 31 | 32 | private final IExpansion expansion; 33 | private final ExpansionConfig configFile; 34 | private final List fieldList; 35 | 36 | public AutoLoadConfiguration(IExpansion expansion) { 37 | this(0, expansion); 38 | } 39 | 40 | public AutoLoadConfiguration(int version, IExpansion expansion) { 41 | this.version = version; 42 | this.expansion = expansion; 43 | this.fieldList = new ArrayList<>(); 44 | String fileName = loadConfigFieldsAndGetFileName(); 45 | this.configFile = new ExpansionConfig(fileName, expansion, version); 46 | loadConfigDataToField(); 47 | } 48 | 49 | @NotNull 50 | @SneakyThrows 51 | private String loadConfigFieldsAndGetFileName() { 52 | final Class clazz = this.getClass(); 53 | final ConfigClass configClass = clazz.getAnnotation(ConfigClass.class); 54 | Assert.notNull(configClass, "Lack of necessary annotation 'ConfigClass'"); 55 | fieldList.addAll(Arrays.asList(clazz.getDeclaredFields())); 56 | if (fieldList.stream().noneMatch(field -> field.getName().equals("version"))) { 57 | fieldList.add(this.getClass().getSuperclass().getDeclaredField("version")); 58 | } 59 | fieldList.forEach(field -> field.setAccessible(true)); 60 | return configClass.name(); 61 | } 62 | 63 | @NotNull 64 | @Override 65 | public Map serialize() { 66 | Map map = new HashMap<>(); 67 | for (Field field : fieldList) { 68 | String key = getFieldRef(field); 69 | try { 70 | Object value = field.get(this); 71 | if (Enum.class.isAssignableFrom(field.getType())) { 72 | value = value.toString(); 73 | } 74 | map.put(key, value); 75 | } catch (IllegalAccessException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | return map; 80 | } 81 | 82 | public ExpansionConfig getConfigFile() { 83 | return configFile; 84 | } 85 | 86 | protected IExpansion getExpansion() { 87 | return expansion; 88 | } 89 | 90 | protected List getFieldList() { 91 | return fieldList; 92 | } 93 | 94 | @SneakyThrows 95 | private void loadConfigDataToField() { 96 | final FileConfiguration config = configFile.getConfig(); 97 | for (Field field : fieldList) { 98 | String key = getFieldRef(field); 99 | Object value = config.get(key); 100 | final Class fieldType = field.getType(); 101 | if (value != null) { 102 | try { 103 | // special field type 104 | if (Map.class.isAssignableFrom(fieldType)) { 105 | Assert.isTrue(value instanceof MemorySection, "Map类型字段配置节点下须有子项!"); 106 | value = ((MemorySection) value).getValues(false); 107 | } else if (Enum.class.isAssignableFrom(fieldType)) { 108 | String[] methods = new String[] {"parse", "get" + fieldType.getSimpleName(), "valueOf"}; 109 | for (String methodName : methods) { 110 | Method method; 111 | try { 112 | method = fieldType.getDeclaredMethod(methodName, String.class); 113 | } catch (NoSuchMethodException e) { 114 | continue; 115 | } 116 | Assert.isTrue(Modifier.isStatic(method.getModifiers()), "Enum cast method must be static!"); 117 | method.setAccessible(true); 118 | value = method.invoke(null, String.valueOf(value)); 119 | break; 120 | } 121 | } 122 | field.set(this, value); 123 | } catch (Exception e) { 124 | BukkitBootstrap.getInstance().getLogger().warning("字段 " + key + " 赋值出错,请检查!"); 125 | e.printStackTrace(); 126 | } 127 | continue; 128 | } 129 | final ConfigField configField = field.getAnnotation(ConfigField.class); 130 | if (configField == null) continue; 131 | final String defaultValue = configField.value(); 132 | if (defaultValue.length() == 0) continue; 133 | // cast support value 134 | field.set(this, AutoConfigUtil.castDefaultBasicType(defaultValue, fieldType)); 135 | } 136 | } 137 | 138 | /** 139 | * 帮助使用者快捷注册静态方法 deserialize / valueOf 的实现 140 | *
141 |      *     public static TestConfig deserialize(Map map) {
142 |      *         return AutoLoadConfiguration.doDeserialize(map, TestConfig.class, expansion);
143 |      *     }
144 |      * 
145 | * */ 146 | @SneakyThrows 147 | public static T doDeserialize(Map map, Class clazz, IExpansion expansion) { 148 | return doDeserialize(map, clazz, 0, expansion); 149 | } 150 | 151 | @SneakyThrows 152 | public static T doDeserialize(Map map, Class clazz, int version, IExpansion expansion) { 153 | // TODO 多级下 map 的结构? 154 | System.out.println("deserialize map:\n" + map); 155 | final Constructor constructor = clazz.getConstructor(int.class, IExpansion.class); 156 | final T instance = constructor.newInstance(version, expansion); 157 | for (Field field : instance.getFieldList()) { 158 | final Object value = map.get(getFieldRef(field)); 159 | if (value != null) 160 | field.set(instance, value); 161 | } 162 | return instance; 163 | } 164 | 165 | 166 | /** 167 | * @apiNote 目标字段应使用驼峰命名法 168 | * */ 169 | @NotNull 170 | private static String getFieldRef(Field field) { 171 | String key = null; 172 | final ConfigField configField = field.getAnnotation(ConfigField.class); 173 | if (configField != null) { 174 | key = configField.ref(); 175 | } 176 | if (key == null || key.length() == 0) { 177 | StringBuilder builder = new StringBuilder(); 178 | for (char c : field.getName().toCharArray()) { 179 | if (65 <= c && c <= 90) 180 | builder.append('-').append((char) (c+32)); 181 | else 182 | builder.append(c); 183 | } 184 | return builder.toString(); 185 | } 186 | return key; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/manager/PluginClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion.manager; 2 | 3 | import com.illtamer.perpetua.sdk.util.Assert; 4 | import org.bukkit.plugin.InvalidPluginException; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.net.URL; 11 | import java.net.URLClassLoader; 12 | import java.util.Enumeration; 13 | import java.util.Map; 14 | import java.util.Set; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | import java.util.jar.JarEntry; 17 | import java.util.jar.JarFile; 18 | import java.util.jar.Manifest; 19 | 20 | /** 21 | * 用于载入一个jar内的所有class 22 | */ 23 | public class PluginClassLoader extends URLClassLoader { 24 | private final Map> classes = new ConcurrentHashMap<>(10); 25 | private final InfinitePluginLoader loader; 26 | private final JarFile jar; 27 | private final File jarFile; 28 | private final URL url; 29 | private final Manifest manifest; 30 | private final String folderName; 31 | final InfiniteExpansion expansion; 32 | // 仅用于判断附属是否已有实例 33 | private InfiniteExpansion expansionInit; 34 | private IllegalStateException expansionState; 35 | 36 | static { // 支持并行加载 37 | ClassLoader.registerAsParallelCapable(); 38 | } 39 | 40 | public PluginClassLoader(InfinitePluginLoader loader, ClassLoader parent, File jarFile) throws IOException, InvalidPluginException { 41 | super(new URL[] {jarFile.toURI().toURL()}, parent); 42 | this.loader = loader; 43 | this.jarFile = jarFile; 44 | this.jar = new JarFile(jarFile); 45 | this.url = jarFile.toURI().toURL(); 46 | this.manifest = jar.getManifest(); 47 | try { 48 | Class mainClass = getExpansionClass(jar, this); 49 | Class expansionClass; 50 | if (mainClass == null) { 51 | throw new InvalidPluginException("Cannot find main class"); 52 | } 53 | this.folderName = mainClass.getSimpleName(); 54 | try { 55 | expansionClass = mainClass.asSubclass(InfiniteExpansion.class); 56 | } catch (ClassCastException ex) { 57 | throw new InvalidPluginException("main class `" + mainClass.getSimpleName() + "' does not extend InfiniteExpansion", ex); 58 | } 59 | 60 | this.expansion = expansionClass.newInstance(); 61 | } catch (IllegalAccessException ex) { 62 | throw new InvalidPluginException("No public constructor", ex); 63 | } catch (InstantiationException ex) { 64 | throw new InvalidPluginException("Abnormal plugin type", ex); 65 | } catch (Error | Exception ex) { 66 | throw new InvalidPluginException(String.format("When enabling jar '%s' there got an Unknown Exception !", jar.getName()), ex); 67 | } 68 | } 69 | 70 | @Override 71 | protected Class findClass(String name) throws ClassNotFoundException { 72 | return findClass(name, true); 73 | } 74 | 75 | Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { 76 | if (name.startsWith("me.illtamer.infinitebot")) { 77 | throw new ClassNotFoundException(name); 78 | } 79 | Class result = this.classes.get(name); 80 | if (result == null && checkGlobal) { // 检查所有同级loader 81 | result = this.loader.getGlobalClassByName(name); 82 | } 83 | if (result == null) { // 导包 84 | JarEntry entry = this.jar.getJarEntry(name.replace('.', '/') + ".class"); 85 | if (entry != null) { 86 | byte[] bytes = getClassBytes(this.jar, entry); 87 | // 未知作用 88 | int dot = name.lastIndexOf('.'); 89 | if (dot != -1) { 90 | String pkgName = name.substring(0, dot); 91 | if (getPackage(pkgName) == null) { 92 | try { 93 | if (this.manifest != null) { 94 | definePackage(pkgName, this.manifest, this.url); 95 | } else { 96 | definePackage(pkgName, null, null, null, null, null, null, null); 97 | } 98 | } catch (IllegalArgumentException ex) { 99 | if (getPackage(pkgName) == null) { 100 | throw new IllegalStateException("Cannot find package " + pkgName); 101 | } 102 | } 103 | } 104 | } 105 | result = defineClass(name, bytes, 0, bytes.length); 106 | } 107 | if (result == null) { // 委派 108 | result = super.findClass(name); 109 | } 110 | if (result != null) { 111 | this.loader.setGlobalClasses(name, result); 112 | } 113 | this.classes.put(name, result); 114 | } 115 | return result; 116 | } 117 | 118 | 119 | public void close() throws IOException { 120 | try { 121 | classes.clear(); 122 | super.close(); 123 | } finally { 124 | this.jar.close(); 125 | System.gc(); 126 | } 127 | } 128 | 129 | /** 130 | * 附属主类构建实例时调用 131 | * @see Class#newInstance() {@link InfiniteExpansion()} 132 | * */ 133 | synchronized void initialize(InfiniteExpansion infiniteExpansion) { 134 | Assert.notNull(infiniteExpansion, "Initializing expansion cannot be null"); 135 | Assert.isTrue((infiniteExpansion.getClass().getClassLoader() == this), "Cannot initialize expansion outside of this class loader"); 136 | if (this.expansion != null || this.expansionInit != null) { 137 | throw new IllegalArgumentException("Expansion already initialized!", this.expansionState); 138 | } 139 | 140 | this.expansionState = new IllegalStateException("Initial initialization"); 141 | this.expansionInit = infiniteExpansion; 142 | 143 | infiniteExpansion.init(this.loader, this.jarFile, this, folderName); 144 | } 145 | 146 | private static byte[] getClassBytes(JarFile jar, JarEntry entry) { 147 | try ( 148 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 149 | InputStream input = jar.getInputStream(entry); 150 | ) { 151 | int line; byte[] buffer = new byte[1024]; 152 | while ((line = input.read(buffer)) != -1) { 153 | out.write(buffer, 0, line); 154 | } 155 | return out.toByteArray(); 156 | } catch (IOException e) { 157 | e.printStackTrace(); 158 | } 159 | return new byte[0]; 160 | } 161 | 162 | private static Class getExpansionClass(JarFile jar, ClassLoader loader) { 163 | Enumeration enumeration = jar.entries(); 164 | int index; 165 | while (enumeration.hasMoreElements()) { 166 | JarEntry entry = enumeration.nextElement(); 167 | if (!entry.isDirectory() && (index = entry.getName().indexOf(".class")) != -1) { 168 | try { 169 | Class clazz = Class.forName(entry.getName().substring(0, index).replace('/', '.'), false, loader); 170 | if (InfiniteExpansion.class.isAssignableFrom(clazz) || (clazz.getSuperclass() != null && "me.illtamer.me.illtamer.infinitebot.expansion.manager.InfiniteExpansion".equals(clazz.getSuperclass().getName()))) { 171 | return Class.forName(entry.getName().substring(0, index).replace('/', '.'), true, loader); 172 | } 173 | } catch (ClassNotFoundException e) { 174 | e.printStackTrace(); 175 | } 176 | } 177 | } 178 | return null; 179 | } 180 | 181 | protected Set getClasses() { 182 | return classes.keySet(); 183 | } 184 | 185 | public URL getResource(String name) { return findResource(name); } 186 | 187 | public Enumeration getResources(String name) throws IOException { return findResources(name); } 188 | } 189 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/ExpansionLoader.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import com.illtamer.infinite.bot.minecraft.api.IExternalExpansion; 5 | import com.illtamer.infinite.bot.minecraft.exception.InvalidExpansionException; 6 | import com.illtamer.infinite.bot.minecraft.expansion.automation.Registration; 7 | import com.illtamer.infinite.bot.minecraft.expansion.manager.InfinitePluginLoader; 8 | import com.illtamer.infinite.bot.minecraft.start.bukkit.BukkitBootstrap; 9 | import com.illtamer.infinite.bot.minecraft.util.ExpansionUtil; 10 | import com.illtamer.perpetua.sdk.util.Assert; 11 | import org.bukkit.plugin.Plugin; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.io.File; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Set; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.logging.Logger; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * 附属总加载类 25 | * */ 26 | public class ExpansionLoader { 27 | private static final InfinitePluginLoader PLUGIN_LOADER = new InfinitePluginLoader(); 28 | private static final Map EXPANSION_MAP = new ConcurrentHashMap<>(); 29 | private static final Map EXTERNAL_EXPANSION_MAP = new ConcurrentHashMap<>(); 30 | private final BukkitBootstrap instance; 31 | private final Logger log; 32 | private final File pluginFolder; 33 | 34 | public ExpansionLoader(BukkitBootstrap instance) { 35 | this.instance = instance; 36 | this.log = instance.getLogger(); 37 | this.pluginFolder = new File(instance.getDataFolder(), "expansions"); 38 | if (!pluginFolder.exists()) { 39 | pluginFolder.mkdirs(); 40 | } 41 | } 42 | 43 | /** 44 | * 加载并初始化附属 45 | * */ 46 | public void loadExpansion(File file) { 47 | if (file.isDirectory() || !file.getName().endsWith(".jar")) { 48 | log.warning("File " + file.getName() + " is not a valid jar file!"); 49 | return; 50 | } 51 | IExpansion expansion = null; 52 | try { 53 | expansion = PLUGIN_LOADER.loadExpansion(file); 54 | EXPANSION_MAP.put(expansion.toString(), expansion); 55 | PLUGIN_LOADER.enableExpansion(expansion); 56 | } catch (InvalidExpansionException e) { 57 | e.printStackTrace(); 58 | } 59 | Assert.isTrue(expansion != null && expansion.isEnabled(), String.format("附属 %s 异常加载!", file.getName())); 60 | } 61 | 62 | /** 63 | * 加载并初始化外部附属 64 | * */ 65 | public void loadExternalExpansion(IExternalExpansion externalExpansion, Plugin plugin) { 66 | if (!externalExpansion.canRegister()) { 67 | externalExpansion.getLogger().info("Registration conditions are not met!"); 68 | return; 69 | } 70 | if (EXTERNAL_EXPANSION_MAP.containsKey(externalExpansion)) { 71 | log.warning("ExternalExpansion " + ExpansionUtil.formatIdentifier(externalExpansion) + " has been repeatedly registered with plugin: " + plugin.getName()); 72 | return; 73 | } 74 | EXTERNAL_EXPANSION_MAP.put(externalExpansion, plugin); 75 | EXPANSION_MAP.put(externalExpansion.toString(), externalExpansion); 76 | PLUGIN_LOADER.enableExternalExpansion(externalExpansion); 77 | Assert.isTrue(externalExpansion.isEnabled(), String.format("附属 %s 异常加载!", ExpansionUtil.formatIdentifier(externalExpansion))); 78 | } 79 | 80 | /** 81 | * 加载并初始化所有插件 82 | * @param persist 是否考虑持久化附属 83 | * */ 84 | public void loadExpansions(boolean persist) { 85 | if (EXPANSION_MAP.size() != 0) { 86 | disableExpansions(persist); 87 | } 88 | File[] expansions = pluginFolder.listFiles((dir, name) -> name.endsWith(".jar")); 89 | if (expansions == null || expansions.length == 0) { 90 | return; 91 | } 92 | int available = 0; 93 | for (File expansion : expansions) { 94 | if (expansion.isDirectory()) continue; 95 | try { 96 | final IExpansion loadExpansion = PLUGIN_LOADER.loadExpansion(expansion); 97 | EXPANSION_MAP.put(loadExpansion.toString(), loadExpansion); 98 | ++ available; 99 | } catch (InvalidExpansionException e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | log.info(String.format("检测到 %d 个内部附属, 正在初始化...", available)); 104 | for (IExpansion expansion : EXPANSION_MAP.values()) { 105 | if (expansion instanceof IExternalExpansion && ((IExternalExpansion) expansion).isPersist()) continue; 106 | Assert.isTrue(!expansion.isEnabled(), String.format("附属 %s 异常加载!", ExpansionUtil.formatIdentifier(expansion))); 107 | PLUGIN_LOADER.enableExpansion(expansion); 108 | } 109 | } 110 | 111 | /** 112 | * 卸载指定附属 113 | * */ 114 | public boolean disableExpansion(String identifier) { 115 | final IExpansion expansion = EXPANSION_MAP.remove(identifier); 116 | if (expansion == null) { 117 | log.warning("指定附属 " + identifier + " 不存在!"); 118 | return false; 119 | } 120 | if (expansion instanceof IExternalExpansion) 121 | return disableExternalExpansion((IExternalExpansion) expansion); 122 | PLUGIN_LOADER.disableExpansion(expansion); 123 | Registration.removeAndStoreAutoConfigs(expansion); 124 | return true; 125 | } 126 | 127 | /** 128 | * 卸载外部附属 129 | * */ 130 | public boolean disableExternalExpansion(IExternalExpansion externalExpansion) { 131 | if (!EXTERNAL_EXPANSION_MAP.containsKey(externalExpansion)) { 132 | log.warning("ExternalExpansion " + ExpansionUtil.formatIdentifier(externalExpansion) + " has not been loaded!"); 133 | return false; 134 | } 135 | PLUGIN_LOADER.disableExternalExpansion(externalExpansion); 136 | EXPANSION_MAP.remove(externalExpansion.toString()); 137 | EXTERNAL_EXPANSION_MAP.remove(externalExpansion); 138 | Registration.removeAndStoreAutoConfigs(externalExpansion); 139 | return true; 140 | } 141 | 142 | /** 143 | * 卸载所有插件并调用其所有onDisable 144 | * @param persist 是否考虑持久化附属 145 | * */ 146 | public void disableExpansions(boolean persist) { 147 | for (Map.Entry entry : EXPANSION_MAP.entrySet()) { 148 | final IExpansion expansion = entry.getValue(); 149 | if (persist && expansion instanceof IExternalExpansion && ((IExternalExpansion) expansion).isPersist()) { 150 | log.info("持久化外部附属 " + entry.getKey() + " 跳过卸载"); 151 | continue; 152 | } 153 | if (expansion instanceof IExternalExpansion) 154 | PLUGIN_LOADER.disableExternalExpansion((IExternalExpansion) expansion); 155 | else 156 | PLUGIN_LOADER.disableExpansion(expansion); 157 | EXPANSION_MAP.remove(entry.getKey()); 158 | Registration.removeAndStoreAutoConfigs(expansion); 159 | } 160 | System.gc(); 161 | } 162 | 163 | @Nullable 164 | public IExpansion getExpansion(String identifier) { 165 | return EXPANSION_MAP.get(identifier); 166 | } 167 | 168 | @Nullable 169 | public IExternalExpansion getExternalExpansion(Plugin plugin) { 170 | for (Map.Entry entry : EXTERNAL_EXPANSION_MAP.entrySet()) 171 | if (entry.getValue().equals(plugin)) return entry.getKey(); 172 | return null; 173 | } 174 | 175 | /** 176 | * 获取所有附属的名称 177 | * @deprecated 附属可能重名 178 | * */ 179 | @Deprecated 180 | @NotNull 181 | public List getExpansionNames() { 182 | return EXPANSION_MAP.values().stream().map(IExpansion::getExpansionName).collect(Collectors.toList()); 183 | } 184 | 185 | /** 186 | * 获取所有附属的完整标识符 187 | * */ 188 | @NotNull 189 | public Set getExpansionKeySet() { 190 | return EXPANSION_MAP.keySet(); 191 | } 192 | 193 | public File getPluginFolder() { 194 | return pluginFolder; 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /minecraft/src/main/java/com/illtamer/infinite/bot/minecraft/expansion/ExpansionLogger.java: -------------------------------------------------------------------------------- 1 | package com.illtamer.infinite.bot.minecraft.expansion; 2 | 3 | import com.illtamer.infinite.bot.minecraft.api.IExpansion; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.slf4j.Marker; 7 | 8 | public class ExpansionLogger implements Logger { 9 | private final Logger logger; 10 | private final String expansionName; 11 | private boolean debug = false; 12 | 13 | public ExpansionLogger(IExpansion expansion) { 14 | String name = expansion.getExpansionName(); 15 | this.expansionName = (name != null && !name.isEmpty()) ? name : expansion.getClass().getSimpleName(); 16 | this.logger = LoggerFactory.getLogger("[InfiniteBot4] [" + expansionName + "]"); 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return expansionName; 22 | } 23 | 24 | @Override 25 | public boolean isTraceEnabled() { 26 | return logger.isTraceEnabled(); 27 | } 28 | 29 | @Override 30 | public void trace(String s) { 31 | logger.trace(s); 32 | } 33 | 34 | @Override 35 | public void trace(String s, Object o) { 36 | logger.trace(s, o); 37 | } 38 | 39 | @Override 40 | public void trace(String s, Object o, Object o1) { 41 | logger.trace(s, o, o1); 42 | } 43 | 44 | @Override 45 | public void trace(String s, Object... objects) { 46 | logger.trace(s, objects); 47 | } 48 | 49 | @Override 50 | public void trace(String s, Throwable throwable) { 51 | logger.trace(s, throwable); 52 | } 53 | 54 | @Override 55 | public boolean isTraceEnabled(Marker marker) { 56 | return false; 57 | } 58 | 59 | @Override 60 | public void trace(Marker marker, String s) { 61 | throw new UnsupportedOperationException(); 62 | } 63 | 64 | @Override 65 | public void trace(Marker marker, String s, Object o) { 66 | throw new UnsupportedOperationException(); 67 | } 68 | 69 | @Override 70 | public void trace(Marker marker, String s, Object o, Object o1) { 71 | throw new UnsupportedOperationException(); 72 | } 73 | 74 | @Override 75 | public void trace(Marker marker, String s, Object... objects) { 76 | throw new UnsupportedOperationException(); 77 | } 78 | 79 | @Override 80 | public void trace(Marker marker, String s, Throwable throwable) { 81 | throw new UnsupportedOperationException(); 82 | } 83 | 84 | @Override 85 | public boolean isDebugEnabled() { 86 | return debug; 87 | } 88 | 89 | @Override 90 | public void debug(String s) { 91 | if (debug) { 92 | logger.debug(s); 93 | } 94 | } 95 | 96 | @Override 97 | public void debug(String s, Object o) { 98 | if (debug) { 99 | logger.debug(s, o); 100 | } 101 | } 102 | 103 | @Override 104 | public void debug(String s, Object o, Object o1) { 105 | if (debug) { 106 | logger.debug(s, o, o1); 107 | } 108 | } 109 | 110 | @Override 111 | public void debug(String s, Object... objects) { 112 | if (debug) { 113 | logger.debug(s, objects); 114 | } 115 | } 116 | 117 | @Override 118 | public void debug(String s, Throwable throwable) { 119 | if (debug) { 120 | logger.debug(s, throwable); 121 | } 122 | } 123 | 124 | @Override 125 | public boolean isDebugEnabled(Marker marker) { 126 | return false; 127 | } 128 | 129 | @Override 130 | public void debug(Marker marker, String s) { 131 | throw new UnsupportedOperationException(); 132 | } 133 | 134 | @Override 135 | public void debug(Marker marker, String s, Object o) { 136 | throw new UnsupportedOperationException(); 137 | } 138 | 139 | @Override 140 | public void debug(Marker marker, String s, Object o, Object o1) { 141 | throw new UnsupportedOperationException(); 142 | } 143 | 144 | @Override 145 | public void debug(Marker marker, String s, Object... objects) { 146 | throw new UnsupportedOperationException(); 147 | } 148 | 149 | @Override 150 | public void debug(Marker marker, String s, Throwable throwable) { 151 | throw new UnsupportedOperationException(); 152 | } 153 | 154 | @Override 155 | public boolean isInfoEnabled() { 156 | return true; 157 | } 158 | 159 | public void info(String msg) { 160 | logger.info(msg); 161 | } 162 | 163 | @Override 164 | public void info(String s, Object o) { 165 | logger.info(s, o); 166 | } 167 | 168 | @Override 169 | public void info(String s, Object o, Object o1) { 170 | logger.info(s, o, o1); 171 | } 172 | 173 | @Override 174 | public void info(String s, Object... objects) { 175 | logger.info(s, objects); 176 | } 177 | 178 | @Override 179 | public void info(String s, Throwable throwable) { 180 | logger.info(s, throwable); 181 | } 182 | 183 | @Override 184 | public boolean isInfoEnabled(Marker marker) { 185 | return false; 186 | } 187 | 188 | @Override 189 | public void info(Marker marker, String s) { 190 | throw new UnsupportedOperationException(); 191 | } 192 | 193 | @Override 194 | public void info(Marker marker, String s, Object o) { 195 | throw new UnsupportedOperationException(); 196 | } 197 | 198 | @Override 199 | public void info(Marker marker, String s, Object o, Object o1) { 200 | throw new UnsupportedOperationException(); 201 | } 202 | 203 | @Override 204 | public void info(Marker marker, String s, Object... objects) { 205 | throw new UnsupportedOperationException(); 206 | } 207 | 208 | @Override 209 | public void info(Marker marker, String s, Throwable throwable) { 210 | throw new UnsupportedOperationException(); 211 | } 212 | 213 | @Override 214 | public boolean isWarnEnabled() { 215 | return true; 216 | } 217 | 218 | @Override 219 | public void warn(String s) { 220 | logger.warn(s); 221 | } 222 | 223 | @Override 224 | public void warn(String s, Object o) { 225 | logger.warn(s, o); 226 | } 227 | 228 | @Override 229 | public void warn(String s, Object... objects) { 230 | logger.warn(s, objects); 231 | } 232 | 233 | @Override 234 | public void warn(String s, Object o, Object o1) { 235 | logger.warn(s, o, o1); 236 | } 237 | 238 | @Override 239 | public void warn(String s, Throwable throwable) { 240 | logger.warn(s, throwable); 241 | } 242 | 243 | @Override 244 | public boolean isWarnEnabled(Marker marker) { 245 | return false; 246 | } 247 | 248 | @Override 249 | public void warn(Marker marker, String s) { 250 | throw new UnsupportedOperationException(); 251 | } 252 | 253 | @Override 254 | public void warn(Marker marker, String s, Object o) { 255 | throw new UnsupportedOperationException(); 256 | } 257 | 258 | @Override 259 | public void warn(Marker marker, String s, Object o, Object o1) { 260 | throw new UnsupportedOperationException(); 261 | } 262 | 263 | @Override 264 | public void warn(Marker marker, String s, Object... objects) { 265 | throw new UnsupportedOperationException(); 266 | } 267 | 268 | @Override 269 | public void warn(Marker marker, String s, Throwable throwable) { 270 | throw new UnsupportedOperationException(); 271 | } 272 | 273 | @Override 274 | public boolean isErrorEnabled() { 275 | return true; 276 | } 277 | 278 | public void error(String msg) { 279 | logger.error(msg); 280 | } 281 | 282 | @Override 283 | public void error(String s, Object o) { 284 | logger.error(s, o); 285 | } 286 | 287 | @Override 288 | public void error(String s, Object o, Object o1) { 289 | logger.error(s, o, o1); 290 | } 291 | 292 | @Override 293 | public void error(String s, Object... objects) { 294 | logger.error(s, objects); 295 | } 296 | 297 | @Override 298 | public void error(String s, Throwable throwable) { 299 | logger.error(s, throwable); 300 | } 301 | 302 | @Override 303 | public boolean isErrorEnabled(Marker marker) { 304 | return false; 305 | } 306 | 307 | @Override 308 | public void error(Marker marker, String s) { 309 | throw new UnsupportedOperationException(); 310 | } 311 | 312 | @Override 313 | public void error(Marker marker, String s, Object o) { 314 | throw new UnsupportedOperationException(); 315 | } 316 | 317 | @Override 318 | public void error(Marker marker, String s, Object o, Object o1) { 319 | throw new UnsupportedOperationException(); 320 | } 321 | 322 | @Override 323 | public void error(Marker marker, String s, Object... objects) { 324 | throw new UnsupportedOperationException(); 325 | } 326 | 327 | @Override 328 | public void error(Marker marker, String s, Throwable throwable) { 329 | throw new UnsupportedOperationException(); 330 | } 331 | 332 | public ExpansionLogger setDebug(boolean debug) { 333 | this.debug = debug; 334 | return this; 335 | } 336 | } 337 | --------------------------------------------------------------------------------