Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
6b34566489 | |||
074e6e8a10 | |||
14cc05e923 | |||
ebc03b6164 | |||
9268ce9fee | |||
3e4453a197 | |||
dc66290f97 | |||
56152657c8 | |||
e2f9bbf587 | |||
a9003025ee | |||
91a87ab20e | |||
ce92ae9ec6 | |||
cfd44a6289 | |||
fc7fb67023 | |||
7b171f1546 | |||
389bfd7137 | |||
f8ba885b8c | |||
48306b64c4 | |||
1cb4c05efd | |||
54e40b6768 | |||
51fc4549da | |||
824c440f63 | |||
7f32062dca | |||
12d07bf552 | |||
0506c9e3e6 | |||
40760713ff | |||
7e36b6109d | |||
7f85295eb1 | |||
427aa262f0 | |||
e71bd2a0cb | |||
9bb67410ac | |||
eba0e18285 | |||
219eb6b919 | |||
17d07d6cef | |||
f2b8f6ff26 | |||
9a15a0ab1f | |||
d0de120867 | |||
351183ab3c | |||
87c78f7c6f | |||
28db77333e | |||
ac83acd4c8 | |||
876c9b77f7 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -53,3 +53,6 @@ vendor/
|
||||
# Minecraft Data
|
||||
/world
|
||||
**/node_modules/
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
|
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
88
README.md
88
README.md
@ -1,87 +1,11 @@
|
||||
# MiaoScript
|
||||
|
||||
> 排版什么的 不存在的 这辈子都不会有排版的 除非什么时候论坛支持 `MarkDown` 了
|
||||
> 一个兼容 Spigot Sponge Nukkit BungeeCord 的 脚本插件运行时
|
||||
|
||||
### 简介
|
||||
## 简介
|
||||
|
||||
> 这个坑是我自己刨的 但是发现坑太大 需要更多的人一起填
|
||||
## 安装
|
||||
|
||||
#### 起源
|
||||
|
||||
- 诞生于 `2016年08月25日` 这是 Git 上的第一个提交 具体啥时候我也忘了
|
||||
- 起初 `MiaoScript` 只是用于服务器其他插件的变量执行 并且依赖于PAPI(不知道是啥的自己百度)
|
||||
- 比如 [`MiaoMenu`](http://w.yumc.pw/zc/MiaoMenu.html) 的部分复杂脚本
|
||||
- 比如 [`MiaoChat`](http://mcbbs.net/thread-631240-1-1.html) 的聊天变量
|
||||
- 突然有一天 圈内的大佬 `QSB` @qiu1995 过来找我 说能不能用脚本监听玩家的事件
|
||||
- PS: 这货自从用过 `DeluxeMenu` 之后就喜欢上了用JS写菜单
|
||||
- 当初感觉没啥问题 就出了第一个简易的 `MiaoScript` 版本 还是用 yml 做的配置文件
|
||||
- 但是由于设计 BukkitAPI 等内容 对Java要求太高 后来 邱也弃坑了 我也弃坑了
|
||||
|
||||
#### 刨坑
|
||||
|
||||
- 时隔多年(也就一年) 看到了Sponge的兴起 (估摸着是MCPC系列的MOD端都弃坑了)
|
||||
- 同时 这期间 收到很多腐竹的单子 但是又是非常基础的东西
|
||||
- 比如 开服给玩家发一条消息啦
|
||||
- 比如 修改玩家某些数据啦
|
||||
- 这些东西实际上也就几行代码的事情
|
||||
- 同时 很多想入坑 插件开发 但是又有一些被卡死在环境搭建上
|
||||
- 比如 `Bukkit` 需要 `BukkitAPI`
|
||||
- `Sponge` 需要 `SpongeAPI` 如果涉及 `MOD` 还要 `Forge` 环境
|
||||
- 再或者 BungeeCord 的插件开发 我也是经常懒得搞
|
||||
- 当然 最主要的是 某个 咕咕咕的群 天天有人问我 喵系插件能不能支持 Sponge
|
||||
- 内心当然是拒绝的 现在要上班养老婆孩子(咳咳 不要以为我是大叔 我也才刚毕业而已) 那里还有时间免费给你们写插件
|
||||
- 于是乎 我又想起了当初的 `MiaoScript`
|
||||
- 突发奇想 一个插件的雏形出现在我的脑海中
|
||||
- 可以兼容多种服务器
|
||||
- 不需要开发环境 有记事本就可以开发
|
||||
- 语法要简单 比如 JavaScript
|
||||
- 能够自动搜索安装依赖(毕竟很多人天天问我为何喵系插件跑不起来 都是缺少PAPI)
|
||||
- 能够不重启更新插件(当然得保证代码安全的前提下)
|
||||
- 在 2017年9月14号(距离 第一个版本正式版发布(2016-09-21) 相差一年整)
|
||||
- 一个全新的 `MiaoScript` 诞生了
|
||||
- Java部分代码 只有一个启动类
|
||||
- 核心全部由 JS 编写
|
||||
- 兼容 `CommonJS` 规范
|
||||
- 实时重载
|
||||
- 不兼容 MOD 服 (咳咳 当然现在已经支持了)
|
||||
- 基础结构如下
|
||||
|
||||
```txt
|
||||
└─src
|
||||
└─main
|
||||
├─java 引导类
|
||||
└─resources
|
||||
├─bios.js 核心启动类 用于释放文件和初始化
|
||||
├─api 全平台兼容的接口
|
||||
├─core 核心代码 例如 require 模块
|
||||
│ └─ext 扩展代码 例如 Object.toJson()
|
||||
├─internal 内部实现 用于各个平台实现API
|
||||
│ ├─bukkit BukkitAPI内部实现
|
||||
│ └─sponge SpongeAPI内部实现
|
||||
├─modules JS模块 例如 js-yaml, http 等
|
||||
└─plugins 这里当然是插件啦
|
||||
├─bukkit 只兼容bukkit的插件
|
||||
├─sponge 只兼容Sponge的插件
|
||||
└─ext 插件扩展类库 用于多个插件共用代码 当然最好是是用 `modules` 啦
|
||||
```
|
||||
- 没错 第一个版本只兼容了 BukkitAPI
|
||||
- 我还用 `MiaoScript` 给某位腐竹写了一个抽奖插件
|
||||
- 当时因为没解决 MOD 服兼容问题 所以就退款了 放上[源码](http://paste.yumc.pw/pknd8q6e1)
|
||||
- 由于当时没有封装相关的API所以很多方法是直接调用了 `Bukkit` 原生的代码
|
||||
- 所以不兼容 `Sponge`
|
||||
|
||||
### 进展
|
||||
|
||||
- [项目发布](https://git.yumc.pw/502647092/MiaoScript/releases)
|
||||
- [项目代码](https://git.yumc.pw/502647092/MiaoScript)
|
||||
- [项目脑图](http://naotu.baidu.com/file/293b9a0fc7cef23c69de81c55e3617d5?token=1eee8fd759198eb7)
|
||||
|
||||
### 规划
|
||||
|
||||
- 初期只会支持JS类型的插件开发
|
||||
- 二期会出一个建议版本的MS脚本 可以用简单的语法实现简单的功能
|
||||
- 各个层级会有依赖控制 比如 `MS脚本 => JS脚本 => 调用Java原生API`
|
||||
|
||||
### 填坑
|
||||
|
||||
- 实际上说了那么多 最终希望的就是 有大佬能一起来填坑 毕竟这个坑太大了
|
||||
- 下载后放入对应服务器目录
|
||||
- Bukkit Nukkit BungeeCord 以及其分支 => `plugins`
|
||||
- Sponge => `mods`
|
||||
|
113
pom.xml
113
pom.xml
@ -1,15 +1,14 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>pw.yumc</groupId>
|
||||
<artifactId>MiaoScript</artifactId>
|
||||
<version>0.3.1</version>
|
||||
<version>0.28.0</version>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>502647092</id>
|
||||
<name>喵♂呜</name>
|
||||
<name>MiaoWoo</name>
|
||||
<email>admin@yumc.pw</email>
|
||||
<url>http://www.yumc.pw</url>
|
||||
<url>https://www.yumc.pw</url>
|
||||
</developer>
|
||||
</developers>
|
||||
<build>
|
||||
@ -17,35 +16,9 @@
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<excludes>
|
||||
<exclude>dev-plugins/**</exclude>
|
||||
</excludes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>org.kamranzafar:jtar</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<ciManagement>
|
||||
<system>Jenkins</system>
|
||||
@ -54,15 +27,65 @@
|
||||
<properties>
|
||||
<env.GIT_COMMIT>DEV</env.GIT_COMMIT>
|
||||
<update.changes>
|
||||
§623-10-25 §afeat: 升级 asm 版本;
|
||||
§623-07-30 §afeat: 优化 require 性能;
|
||||
§cfix: 修复 require 加载特殊文件异常;
|
||||
§623-07-22 §afeat: 网络加载 jtar 优化包大小;
|
||||
§aremove: 移除 Spring 相关支持;
|
||||
§adeps: 更新 Nashorn 和 GraalvmJS 引擎版本
|
||||
</update.changes>
|
||||
<update.changelog>
|
||||
§622-11-22 §afeat: 兼容 1.7.10-1.19.2 版本;
|
||||
§afeat: 新增 MiaoScriptAPI 相关方法;
|
||||
§622-06-21 §afeat: 兼容 1.7.10-1.19 版本;
|
||||
§afeat: 兼容 JDK17 BungeeCord;
|
||||
§622-05-25 §afeat: 兼容 1.7.10-1.18.2 版本;
|
||||
§622-05-21 §afeat: 优化 框架加载逻辑;
|
||||
§622-05-20 §afeat: 调整 require 主包逻辑;
|
||||
§622-04-09 §afeat: 优化 引擎初始化逻辑;
|
||||
§afeat: 优化 require 网络加载;
|
||||
§afeat: 新增 JS 类型定义文件;
|
||||
§afeat: 新增 自定义类型加载逻辑;
|
||||
§622-02-16 §afeat: 新增 MiaoScriptAPI;
|
||||
§afeat: 新增 ScriptEvent;
|
||||
§afeat: 新增 .mjs .json 类型加载;
|
||||
§622-02-16 §afeat: 优化 初始化逻辑 加快引擎加载速度;
|
||||
§afeat: 新增 root 目录变更检测 变更后重新生成缓存;
|
||||
§afeat: 添加常用库的软依赖;
|
||||
§622-01-01 §afeat: 兼容JDK17 更新Nashorn;
|
||||
§621-11-04 §afeat: 优化系统模块升级逻辑;
|
||||
§621-07-10 §afeat: 优化网络相关功能;
|
||||
§621-06-25 §afeat: 调整启动逻辑 兼容 Arclight;
|
||||
§621-06-22 §afeat: 新增本地版本锁定功能;
|
||||
§621-06-19 §afeat: 兼容JDK16 反射异常;
|
||||
§621-05-15 §afeat: 兼容JDK15+ 自动下载Nashorn类库;
|
||||
§621-03-25 §afeat: 异步加载 polyfill 并且同步加载 @ccms/core;
|
||||
§621-03-25 §cfix: 修改 ployfill 为 polyfill;
|
||||
§620-12-22 §cfix: 增加 require 效验;
|
||||
§620-12-17 §afeat: JavaScriptTask 新增任务ID 并通过 ID 比较优先级;
|
||||
§620-12-16 §afeat: 新增 require 缓存 优化路径寻找速度;
|
||||
§620-12-15 §cfix: 修复 非主线程重载时发生的异常;
|
||||
§620-12-07 §cfix: 修复 Windows 环境 重载异常;
|
||||
§620-11-19 §afeat: 新增 JavaScriptTask 类;
|
||||
§620-11-11 §afeat: 新增 package 版本锁定逻辑;
|
||||
§620-09-21 §afeat: 完善 upgrade 逻辑;
|
||||
§620-08-27 §afeat: 新增 ProtocolLib 依赖;
|
||||
§620-07-28 §afeat: 新增框架升级功能;
|
||||
§620-06-23 §afeat: 支持自定义参数;
|
||||
§620-06-22 §afeat: 优化 require 加载逻辑;
|
||||
§620-05-28 §afeat: 新增 Spring 的支持;
|
||||
§620-05-02 §afeat: 调整 scope 为 @ccms;
|
||||
§620-04-10 §afeat: 默认从 classpath 加载内建的 js 模块;
|
||||
§620-04-07 §afeat: 默认初始化 内建 nodejs 模块;
|
||||
§620-04-03 §afeat: 优化 框架卸载逻辑;
|
||||
§620-03-31 §afeat: require 新增 内建 nodejs 模块;
|
||||
§620-03-03 §afeat: require 新增 淘宝镜像源拉取;
|
||||
§620-02-27 §afeat: 异步加载脚本引擎;
|
||||
§620-02-25 §afeat: 新增 Nukkit 的支持;
|
||||
§620-02-16 §afeat: 新增 Source Map 支持;
|
||||
§620-02-02 §afeat: 迁移 ployfill 到 @ms/ployfill;
|
||||
§620-01-14 §afeat: 新增 Bungee 支持;
|
||||
§afeat: 新增 instance 实例获取
|
||||
</update.changes>
|
||||
<update.changelog>
|
||||
§afeat: 新增 instance 实例获取;
|
||||
§619-09-24 §cremove: 移除 okhttp3 类库;
|
||||
§afeat: 新增require自动下载模块功能;
|
||||
§619-09-21 §afeat: 新增 okhttp3 类库;
|
||||
@ -143,10 +166,6 @@
|
||||
<id>yumc-repo</id>
|
||||
<url>${repo.url}/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sponge</id>
|
||||
<url>https://repo.spongepowered.org/maven/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
@ -165,32 +184,32 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kamranzafar</groupId>
|
||||
<artifactId>jtar</artifactId>
|
||||
<version>2.3</version>
|
||||
<version>1.18.24</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.14.4-R0.1-SNAPSHOT</version>
|
||||
<version>1.18.2-R0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>spongeapi</artifactId>
|
||||
<version>7.1.0</version>
|
||||
<version>7.3.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<version>1.16-R0.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.nukkit</groupId>
|
||||
<artifactId>nukkit</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -1,60 +0,0 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.val;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* Created on 2017/10/9 12:40.
|
||||
*/
|
||||
public class Base {
|
||||
private Object instance;
|
||||
|
||||
public Base(Object instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public Class getClass(String name) throws ClassNotFoundException {
|
||||
return Class.forName(name);
|
||||
}
|
||||
|
||||
public Object getInstance() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public Class getProxyClass() {
|
||||
return ProxyClass.class;
|
||||
}
|
||||
|
||||
public String read(String path) throws IOException {
|
||||
return new String(Files.readAllBytes(new File(path).toPath()), "UTF-8");
|
||||
}
|
||||
|
||||
public void save(String path, String content) throws IOException {
|
||||
File file = new File(path);
|
||||
file.getParentFile().mkdirs();
|
||||
Files.write(file.toPath(), content.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
public void delete(String path) throws IOException {
|
||||
delete(new File(path).toPath());
|
||||
}
|
||||
|
||||
public void delete(Path path) throws IOException {
|
||||
val file = path.toFile();
|
||||
if (!file.exists()) { return; }
|
||||
if (file.isDirectory()) {
|
||||
for (Path f : Files.list(file.toPath()).collect(Collectors.toList())) {
|
||||
delete(f);
|
||||
}
|
||||
}
|
||||
Files.delete(path);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
/**
|
||||
* 喵式脚本
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* @since 2016年8月29日 上午7:50:39
|
||||
*/
|
||||
public class MiaoScript extends JavaPlugin {
|
||||
private ScriptEngine engine;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onEnable() {
|
||||
Thread.currentThread().setContextClassLoader(getClassLoader());
|
||||
engine = new ScriptEngine(getDataFolder().getCanonicalPath(), getLogger(), this);
|
||||
engine.enableEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
engine.disableEngine();
|
||||
}
|
||||
}
|
40
src/main/java/pw/yumc/MiaoScript/MiaoScriptBukkit.java
Normal file
40
src/main/java/pw/yumc/MiaoScript/MiaoScriptBukkit.java
Normal file
@ -0,0 +1,40 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import pw.yumc.MiaoScript.api.MiaoScriptAPI;
|
||||
import pw.yumc.MiaoScript.api.ScriptEngine;
|
||||
|
||||
/**
|
||||
* 喵式脚本
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* @since 2016年8月29日 上午7:50:39
|
||||
*/
|
||||
public class MiaoScriptBukkit extends JavaPlugin {
|
||||
private ScriptEngine engine;
|
||||
|
||||
@SneakyThrows
|
||||
public MiaoScriptBukkit() {
|
||||
ClassLoader origin = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(getClassLoader());
|
||||
engine = MiaoScriptAPI.createEngine(getDataFolder().getCanonicalPath(), getLogger(), this);
|
||||
Thread.currentThread().setContextClassLoader(origin);
|
||||
engine.loadEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
engine.enableEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
engine.disableEngine();
|
||||
engine = null;
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ package pw.yumc.MiaoScript;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import pw.yumc.MiaoScript.api.MiaoScriptAPI;
|
||||
import pw.yumc.MiaoScript.api.ScriptEngine;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA
|
||||
@ -12,16 +14,25 @@ import net.md_5.bungee.api.plugin.Plugin;
|
||||
public class MiaoScriptBungee extends Plugin {
|
||||
private ScriptEngine engine;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onEnable() {
|
||||
public MiaoScriptBungee() {
|
||||
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
|
||||
engine = new ScriptEngine(getDataFolder().getCanonicalPath(), getLogger(), this);
|
||||
engine = MiaoScriptAPI.createEngine(getDataFolder().getCanonicalPath(), getLogger(), this);
|
||||
engine.loadEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
engine.enableEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
engine.disableEngine();
|
||||
engine = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,182 +0,0 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineFactory;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import javax.script.SimpleBindings;
|
||||
|
||||
import lombok.val;
|
||||
|
||||
/**
|
||||
* 喵式脚本引擎
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* @since 2016年8月29日 上午7:51:43
|
||||
*/
|
||||
public class MiaoScriptEngine implements ScriptEngine, Invocable {
|
||||
private static MiaoScriptEngine DEFAULT;
|
||||
private static ScriptEngineManager manager;
|
||||
private ScriptEngine engine;
|
||||
|
||||
static {
|
||||
manager = new ScriptEngineManager(ClassLoader.getSystemClassLoader());
|
||||
}
|
||||
|
||||
public static void setBindings(Bindings bindings) {
|
||||
manager.setBindings(bindings);
|
||||
}
|
||||
|
||||
public static Bindings getBindings() {
|
||||
return manager.getBindings();
|
||||
}
|
||||
|
||||
public MiaoScriptEngine() {
|
||||
this("js");
|
||||
}
|
||||
|
||||
public MiaoScriptEngine(final String engineType) {
|
||||
this(manager, engineType);
|
||||
}
|
||||
|
||||
public MiaoScriptEngine(ScriptEngineManager engineManager) {
|
||||
this(engineManager, "js");
|
||||
}
|
||||
|
||||
public MiaoScriptEngine(ScriptEngineManager engineManager, final String engineType) {
|
||||
try {
|
||||
engine = engineManager.getEngineByName(engineType);
|
||||
} catch (final NullPointerException ignored) {
|
||||
}
|
||||
if (engine == null) {
|
||||
val dirs = System.getProperty("java.ext.dirs").split(File.pathSeparator);
|
||||
for (String dir : dirs) {
|
||||
File nashorn = new File(dir, "nashorn.jar");
|
||||
if (nashorn.exists()) {
|
||||
try {
|
||||
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
|
||||
// 设置方法的访问权限
|
||||
method.setAccessible(true);
|
||||
// 获取系统类加载器
|
||||
URL url = nashorn.toURI().toURL();
|
||||
method.invoke(Thread.currentThread().getContextClassLoader(), url);
|
||||
engineManager = new ScriptEngineManager();
|
||||
engine = engineManager.getEngineByName(engineType);
|
||||
} catch (NoSuchMethodException | MalformedURLException | IllegalAccessException | InvocationTargetException | NullPointerException ignored) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException("当前环境不支持 " + engineType + " 脚本类型!");
|
||||
}
|
||||
}
|
||||
|
||||
public static MiaoScriptEngine getDefault() {
|
||||
if (DEFAULT == null) {
|
||||
DEFAULT = new MiaoScriptEngine();
|
||||
}
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bindings createBindings() {
|
||||
return new SimpleBindings(new HashMap<>(engine.getBindings(ScriptContext.GLOBAL_SCOPE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final Reader reader) throws ScriptException {
|
||||
return engine.eval(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final Reader reader, final Bindings n) throws ScriptException {
|
||||
return engine.eval(reader, n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final Reader reader, final ScriptContext context) throws ScriptException {
|
||||
return engine.eval(reader, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final String script) throws ScriptException {
|
||||
return engine.eval(script);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final String script, final Bindings n) throws ScriptException {
|
||||
return engine.eval(script, n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final String script, final ScriptContext context) throws ScriptException {
|
||||
return engine.eval(script, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(final String key) {
|
||||
return engine.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bindings getBindings(final int scope) {
|
||||
return engine.getBindings(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptContext getContext() {
|
||||
return engine.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptEngineFactory getFactory() {
|
||||
return engine.getFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getInterface(final Class<T> clasz) {
|
||||
return ((Invocable) engine).getInterface(clasz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getInterface(final Object thiz, final Class<T> clasz) {
|
||||
return ((Invocable) engine).getInterface(thiz, clasz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeFunction(final String name, final Object... args) throws ScriptException, NoSuchMethodException {
|
||||
return ((Invocable) engine).invokeFunction(name, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeMethod(final Object thiz, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
|
||||
return ((Invocable) engine).invokeMethod(thiz, name, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final String key, final Object value) {
|
||||
engine.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBindings(final Bindings bindings, final int scope) {
|
||||
engine.setBindings(bindings, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(final ScriptContext context) {
|
||||
engine.setContext(context);
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ package pw.yumc.MiaoScript;
|
||||
|
||||
import cn.nukkit.plugin.PluginBase;
|
||||
import lombok.SneakyThrows;
|
||||
import pw.yumc.MiaoScript.api.MiaoScriptAPI;
|
||||
import pw.yumc.MiaoScript.api.ScriptEngine;
|
||||
|
||||
/**
|
||||
* @author MiaoWoo
|
||||
@ -9,16 +11,25 @@ import lombok.SneakyThrows;
|
||||
public class MiaoScriptNukkit extends PluginBase {
|
||||
private ScriptEngine engine;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onEnable() {
|
||||
public MiaoScriptNukkit() {
|
||||
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
|
||||
engine = new ScriptEngine(getDataFolder().getCanonicalPath(), getLogger(), this);
|
||||
engine = MiaoScriptAPI.createEngine(getDataFolder().getCanonicalPath(), super.getLogger(), this);
|
||||
engine.loadEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
engine.enableEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
engine.disableEngine();
|
||||
engine = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import lombok.SneakyThrows;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.api.config.ConfigDir;
|
||||
import org.spongepowered.api.event.Listener;
|
||||
import org.spongepowered.api.event.game.GameReloadEvent;
|
||||
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
|
||||
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
|
||||
import org.spongepowered.api.event.game.state.GameStoppingServerEvent;
|
||||
import org.spongepowered.api.plugin.Plugin;
|
||||
import pw.yumc.MiaoScript.api.MiaoScriptAPI;
|
||||
import pw.yumc.MiaoScript.api.ScriptEngine;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import lombok.SneakyThrows;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA
|
||||
@ -19,7 +21,7 @@ import lombok.SneakyThrows;
|
||||
* @author 喵♂呜
|
||||
* Created on 2017/10/25 20:35.
|
||||
*/
|
||||
@Plugin(id = "miaoscript", name = "MiaoScript", version = "1.0", authors = "喵♂呜")
|
||||
@Plugin(id = "miaoscript", name = "MiaoScript", description = "MiaoScript runtime in Sponge", version = MiaoScriptAPI.VERSION, authors = "MiaoWoo")
|
||||
public class MiaoScriptSponge {
|
||||
private ScriptEngine engine;
|
||||
@Inject
|
||||
@ -31,8 +33,14 @@ public class MiaoScriptSponge {
|
||||
|
||||
@Listener
|
||||
@SneakyThrows
|
||||
public void onStart(GameStartedServerEvent event) {
|
||||
engine = new ScriptEngine(pluginConfigDir.getCanonicalPath(), logger, this);
|
||||
public void onPreInitialization(GamePreInitializationEvent event) {
|
||||
engine = MiaoScriptAPI.createEngine(pluginConfigDir.getCanonicalPath(), logger, this);
|
||||
engine.loadEngine();
|
||||
}
|
||||
|
||||
@Listener
|
||||
@SneakyThrows
|
||||
public void onStarted(GameStartedServerEvent event) {
|
||||
engine.enableEngine();
|
||||
}
|
||||
|
||||
@ -40,13 +48,15 @@ public class MiaoScriptSponge {
|
||||
@SneakyThrows
|
||||
public void onStop(GameStoppingServerEvent event) {
|
||||
engine.disableEngine();
|
||||
engine = null;
|
||||
}
|
||||
|
||||
@Listener
|
||||
@SneakyThrows
|
||||
public void reload(GameReloadEvent event) {
|
||||
engine.disableEngine();
|
||||
engine = new ScriptEngine(pluginConfigDir.getCanonicalPath(), logger, this);
|
||||
System.gc();
|
||||
engine.loadEngine();
|
||||
engine.enableEngine();
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* Created on 2017/10/25 21:01.
|
||||
*/
|
||||
public class ScriptEngine {
|
||||
private String root;
|
||||
private Object logger;
|
||||
private Base base;
|
||||
private MiaoScriptEngine engine;
|
||||
private ScriptEngineManager manager;
|
||||
|
||||
public ScriptEngine(String root, Object logger, Object instance) {
|
||||
this.root = root;
|
||||
this.logger = logger;
|
||||
this.base = new Base(instance);
|
||||
this.manager = new ScriptEngineManager();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void enableEngine() {
|
||||
this.engine = new MiaoScriptEngine(manager, "nashorn");
|
||||
this.engine.put("base", this.base);
|
||||
this.engine.put("ScriptEngineContextHolder", this);
|
||||
Path bios = Paths.get(root, "bios.js");
|
||||
// 如果存在自定义bios就加载自定义的
|
||||
if (Files.exists(bios)) {
|
||||
this.engine.eval("load('" + bios.toFile().getCanonicalPath() + "')");
|
||||
} else {
|
||||
this.engine.eval(new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("bios.js")));
|
||||
}
|
||||
engine.invokeFunction("boot", root, logger);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void disableEngine() {
|
||||
this.engine.invokeFunction("engineDisable");
|
||||
this.engine = null;
|
||||
}
|
||||
|
||||
public MiaoScriptEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
}
|
128
src/main/java/pw/yumc/MiaoScript/api/Base.java
Normal file
128
src/main/java/pw/yumc/MiaoScript/api/Base.java
Normal file
@ -0,0 +1,128 @@
|
||||
package pw.yumc.MiaoScript.api;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* Created on 2017/10/9 12:40.
|
||||
*/
|
||||
@Getter
|
||||
public class Base {
|
||||
private final Object instance;
|
||||
|
||||
Base(Object instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return MiaoScriptAPI.VERSION;
|
||||
}
|
||||
|
||||
public Class<?> getClass(String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName(name);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
try {
|
||||
return Class.forName(name, true, instance.getClass().getClassLoader());
|
||||
} catch (Throwable ex) {
|
||||
return Class.forName(name, true, instance.getClass().getClassLoader().getParent());
|
||||
}
|
||||
}
|
||||
|
||||
public Class<?> getProxyClass() {
|
||||
return ProxyClass.class;
|
||||
}
|
||||
|
||||
public Class<?> getJavaScriptTaskClass() {
|
||||
return JavaScriptTask.class;
|
||||
}
|
||||
|
||||
public File[] loadMavenDepend(String groupId, String artifactId, String version) {
|
||||
return MiaoScriptAPI.loadMavenDepend(groupId, artifactId, version);
|
||||
}
|
||||
|
||||
public File[] loadMavenDepend(String groupId, String artifactId, String version, ClassLoader classLoader) {
|
||||
return MiaoScriptAPI.loadMavenDepend(groupId, artifactId, version, classLoader);
|
||||
}
|
||||
|
||||
public File[] parentLoadMavenDepend(String groupId, String artifactId, String version) {
|
||||
return MiaoScriptAPI.parentLoadMavenDepend(groupId, artifactId, version);
|
||||
}
|
||||
|
||||
public String read(String path) throws IOException {
|
||||
return read(Paths.get(path));
|
||||
}
|
||||
|
||||
public String read(File file) throws IOException {
|
||||
return read(file.toPath());
|
||||
}
|
||||
|
||||
public String read(Path path) throws IOException {
|
||||
return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public Path save(String path, String content) throws IOException {
|
||||
return save(Paths.get(path), content);
|
||||
}
|
||||
|
||||
public Path save(File file, String content) throws IOException {
|
||||
return save(file.toPath(), content);
|
||||
}
|
||||
|
||||
public Path save(Path path, String content) throws IOException {
|
||||
path.getParent().toFile().mkdirs();
|
||||
return Files.write(path, content.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public boolean move(String source, String target) {
|
||||
return move(new File(source), new File(target));
|
||||
}
|
||||
|
||||
public boolean move(File source, File target) {
|
||||
return source.renameTo(target);
|
||||
}
|
||||
|
||||
public boolean delete(String path) throws IOException {
|
||||
return delete(new File(path));
|
||||
}
|
||||
|
||||
public boolean delete(Path path) throws IOException {
|
||||
return delete(path.toFile());
|
||||
}
|
||||
|
||||
public boolean delete(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
if (file.isFile()) {
|
||||
return file.delete();
|
||||
}
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.isFile()) {
|
||||
if (!f.delete()) {
|
||||
f.deleteOnExit();
|
||||
}
|
||||
} else {
|
||||
this.delete(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean result = file.delete();
|
||||
if (!result) {
|
||||
file.deleteOnExit();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
41
src/main/java/pw/yumc/MiaoScript/api/JavaScriptTask.java
Normal file
41
src/main/java/pw/yumc/MiaoScript/api/JavaScriptTask.java
Normal file
@ -0,0 +1,41 @@
|
||||
package pw.yumc.MiaoScript.api;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter
|
||||
public class JavaScriptTask implements Delayed {
|
||||
private final long id;
|
||||
private final Object task;
|
||||
private final long startTime;
|
||||
private final long executeTime;
|
||||
|
||||
public JavaScriptTask(Object task, long ms) {
|
||||
this(0, task, ms);
|
||||
}
|
||||
|
||||
public JavaScriptTask(long id, Object task, long ms) {
|
||||
this.id = id;
|
||||
this.task = task;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.executeTime = ms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDelay(TimeUnit unit) {
|
||||
return unit.convert((this.startTime + this.executeTime) - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Delayed delayed) {
|
||||
JavaScriptTask task = (JavaScriptTask) delayed;
|
||||
int delay = (int) ((this.startTime + this.executeTime) - (task.getStartTime() + task.getExecuteTime()));
|
||||
if (delay != 0) {
|
||||
return delay;
|
||||
} else {
|
||||
return (int) (this.id - task.getId());
|
||||
}
|
||||
}
|
||||
}
|
62
src/main/java/pw/yumc/MiaoScript/api/MiaoScriptAPI.java
Normal file
62
src/main/java/pw/yumc/MiaoScript/api/MiaoScriptAPI.java
Normal file
@ -0,0 +1,62 @@
|
||||
package pw.yumc.MiaoScript.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import pw.yumc.MiaoScript.api.loader.MavenDependLoader;
|
||||
import pw.yumc.MiaoScript.api.plugin.PluginManager;
|
||||
import pw.yumc.MiaoScript.engine.MiaoScriptEngine;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class MiaoScriptAPI {
|
||||
public static final String VERSION = "0.28.0";
|
||||
@Getter
|
||||
private static String root;
|
||||
private static String libPath;
|
||||
private static ScriptEngine scriptEngine;
|
||||
@Getter
|
||||
private static PluginManager pluginManager;
|
||||
|
||||
public static ScriptEngine createEngine(String root, Object logger, Object instance) {
|
||||
MiaoScriptAPI.scriptEngine = new ScriptEngine(root, logger, instance);
|
||||
return MiaoScriptAPI.scriptEngine;
|
||||
}
|
||||
|
||||
static void setRoot(String root) {
|
||||
MiaoScriptAPI.root = root;
|
||||
MiaoScriptAPI.libPath = Paths.get(root, "libs").toString();
|
||||
}
|
||||
|
||||
public static MiaoScriptEngine getEngine() {
|
||||
return MiaoScriptAPI.scriptEngine.getEngine();
|
||||
}
|
||||
|
||||
static void setEngine(ScriptEngine scriptEngine) {
|
||||
MiaoScriptAPI.scriptEngine = scriptEngine;
|
||||
}
|
||||
|
||||
public static void setPluginManager(Object pluginManager) {
|
||||
MiaoScriptAPI.pluginManager = getEngine().getInterface(pluginManager, PluginManager.class);
|
||||
}
|
||||
|
||||
public static File[] loadMavenDepend(String groupId, String artifactId, String version) {
|
||||
if (root == null || scriptEngine == null) {
|
||||
throw new IllegalStateException("root can't be null before loadMavenDepend.");
|
||||
}
|
||||
return MavenDependLoader.load(MiaoScriptAPI.libPath, groupId, artifactId, version);
|
||||
}
|
||||
|
||||
public static File[] loadMavenDepend(String groupId, String artifactId, String version, ClassLoader classLoader) {
|
||||
if (root == null || scriptEngine == null) {
|
||||
throw new IllegalStateException("root can't be null before loadMavenDepend.");
|
||||
}
|
||||
return MavenDependLoader.load(MiaoScriptAPI.libPath, groupId, artifactId, version, classLoader);
|
||||
}
|
||||
|
||||
public static File[] parentLoadMavenDepend(String groupId, String artifactId, String version) {
|
||||
if (root == null || scriptEngine == null) {
|
||||
throw new IllegalStateException("root can't be null before loadMavenDepend.");
|
||||
}
|
||||
return MavenDependLoader.parentLoad(MiaoScriptAPI.libPath, groupId, artifactId, version);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package pw.yumc.MiaoScript;
|
||||
package pw.yumc.MiaoScript.api;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.ScriptEngine;
|
76
src/main/java/pw/yumc/MiaoScript/api/ScriptEngine.java
Normal file
76
src/main/java/pw/yumc/MiaoScript/api/ScriptEngine.java
Normal file
@ -0,0 +1,76 @@
|
||||
package pw.yumc.MiaoScript.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import pw.yumc.MiaoScript.engine.MiaoScriptEngine;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA
|
||||
*
|
||||
* @author 喵♂呜 Created on 2017/10/25 21:01.
|
||||
*/
|
||||
public class ScriptEngine {
|
||||
private final ClassLoader loader;
|
||||
private final Object logger;
|
||||
private final String root;
|
||||
private final Base base;
|
||||
@Getter
|
||||
private MiaoScriptEngine engine;
|
||||
private Object future;
|
||||
|
||||
ScriptEngine(String root, Object logger, Object instance) {
|
||||
this.loader = Thread.currentThread().getContextClassLoader();
|
||||
this.root = root;
|
||||
this.logger = logger;
|
||||
this.base = new Base(instance);
|
||||
MiaoScriptAPI.setRoot(root);
|
||||
MiaoScriptAPI.setEngine(this);
|
||||
}
|
||||
|
||||
public void createEngine() {
|
||||
synchronized (logger) {
|
||||
if (this.engine == null) {
|
||||
this.engine = new MiaoScriptEngine(root);
|
||||
this.engine.put("base", this.base);
|
||||
this.engine.put("ScriptEngineContextHolder", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void loadEngine() {
|
||||
ClassLoader originLoader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(this.loader);
|
||||
createEngine();
|
||||
Path bios = Paths.get(root, "bios.js");
|
||||
// 如果存在自定义bios就加载自定义的
|
||||
if (Files.exists(bios)) {
|
||||
this.engine.eval("load('" + bios.toFile().getCanonicalPath() + "')");
|
||||
} else {
|
||||
this.engine.eval("load('classpath:bios.js')");
|
||||
}
|
||||
future = engine.invokeFunction("boot", root, logger);
|
||||
Thread.currentThread().setContextClassLoader(originLoader);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void enableEngine() {
|
||||
if (this.engine != null) {
|
||||
engine.invokeFunction("enable", future);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void disableEngine() {
|
||||
synchronized (logger) {
|
||||
if (this.engine != null) {
|
||||
this.engine.invokeFunction("disable");
|
||||
this.engine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
src/main/java/pw/yumc/MiaoScript/api/bukkit/EngineEvent.java
Normal file
39
src/main/java/pw/yumc/MiaoScript/api/bukkit/EngineEvent.java
Normal file
@ -0,0 +1,39 @@
|
||||
package pw.yumc.MiaoScript.api.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import javax.script.Bindings;
|
||||
|
||||
@Getter
|
||||
public class EngineEvent extends Event implements Cancellable {
|
||||
private final String event;
|
||||
private final Bindings data;
|
||||
|
||||
private boolean cancelled = false;
|
||||
|
||||
public EngineEvent(String event, Bindings data) {
|
||||
this.event = event;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean b) {
|
||||
this.cancelled = b;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static final HandlerList handlerList = new HandlerList();
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlerList;
|
||||
}
|
||||
}
|
41
src/main/java/pw/yumc/MiaoScript/api/bukkit/ScriptEvent.java
Normal file
41
src/main/java/pw/yumc/MiaoScript/api/bukkit/ScriptEvent.java
Normal file
@ -0,0 +1,41 @@
|
||||
package pw.yumc.MiaoScript.api.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import javax.script.Bindings;
|
||||
|
||||
@Getter
|
||||
public class ScriptEvent extends Event implements Cancellable {
|
||||
private final Bindings plugin;
|
||||
private final String event;
|
||||
private final Bindings data;
|
||||
|
||||
private boolean cancelled = false;
|
||||
|
||||
public ScriptEvent(Bindings plugin, String event, Bindings data) {
|
||||
this.plugin = plugin;
|
||||
this.event = event;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean b) {
|
||||
this.cancelled = b;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static final HandlerList handlerList = new HandlerList();
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlerList;
|
||||
}
|
||||
}
|
67
src/main/java/pw/yumc/MiaoScript/api/loader/JarLoader.java
Normal file
67
src/main/java/pw/yumc/MiaoScript/api/loader/JarLoader.java
Normal file
@ -0,0 +1,67 @@
|
||||
package pw.yumc.MiaoScript.api.loader;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
|
||||
public class JarLoader {
|
||||
private static sun.misc.Unsafe unsafe;
|
||||
private static long offset;
|
||||
private static Object parentUcp;
|
||||
private static Object ucp;
|
||||
private static MethodHandle addURLMethodHandle;
|
||||
|
||||
static {
|
||||
initReflect();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static File load(File file) {
|
||||
addURLMethodHandle.invoke(ucp, file.toURI().toURL());
|
||||
return file;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static File parentLoad(File file) {
|
||||
if (parentUcp == null)
|
||||
throw new IllegalStateException("parentUcp is null.");
|
||||
addURLMethodHandle.invoke(parentUcp, file.toURI().toURL());
|
||||
return file;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static File load(File file, ClassLoader loader) {
|
||||
addURLMethodHandle.invoke(unsafe.getObject(loader, offset), file.toURI().toURL());
|
||||
return file;
|
||||
}
|
||||
|
||||
private static void initReflect() {
|
||||
try {
|
||||
ClassLoader loader = JarLoader.class.getClassLoader();
|
||||
Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
unsafe = (sun.misc.Unsafe) theUnsafe.get(null);
|
||||
Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
|
||||
MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field));
|
||||
Field ucpField;
|
||||
try {
|
||||
ucpField = loader.getClass().getDeclaredField("ucp");
|
||||
} catch (NoSuchFieldException e) {
|
||||
ucpField = loader.getClass().getSuperclass().getDeclaredField("ucp");
|
||||
}
|
||||
offset = unsafe.objectFieldOffset(ucpField);
|
||||
ucp = unsafe.getObject(loader, offset);
|
||||
Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class);
|
||||
addURLMethodHandle = lookup.unreflect(method);
|
||||
if (loader.getParent() != null)
|
||||
parentUcp = unsafe.getObject(loader.getParent(), offset);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package pw.yumc.MiaoScript.api.loader;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class MavenDependLoader {
|
||||
public static final String MavenRepo = "https://maven.aliyun.com/repository/public";
|
||||
|
||||
public static File[] load(String libPath, String groupId, String artifactId, String version) {
|
||||
return new File[]{
|
||||
downloadAndCheckSha1(libPath, groupId, artifactId, version, "pom"),
|
||||
JarLoader.load(downloadAndCheckSha1(libPath, groupId, artifactId, version, "jar"))
|
||||
};
|
||||
}
|
||||
|
||||
public static File[] parentLoad(String libPath, String groupId, String artifactId, String version) {
|
||||
return new File[]{
|
||||
downloadAndCheckSha1(libPath, groupId, artifactId, version, "pom"),
|
||||
JarLoader.parentLoad(downloadAndCheckSha1(libPath, groupId, artifactId, version, "jar"))
|
||||
};
|
||||
}
|
||||
|
||||
public static File[] load(String libPath, String groupId, String artifactId, String version, ClassLoader loader) {
|
||||
return new File[]{
|
||||
downloadAndCheckSha1(libPath, groupId, artifactId, version, "pom"),
|
||||
JarLoader.load(downloadAndCheckSha1(libPath, groupId, artifactId, version, "jar"), loader)
|
||||
};
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static File downloadAndCheckSha1(String libPath, String groupId, String artifactId, String version, String ext) {
|
||||
File sha1 = getMavenFile(libPath, groupId, artifactId, version, ext + ".sha1");
|
||||
if (!sha1.exists()) {
|
||||
downloadFile(sha1, groupId, artifactId, version, ext + ".sha1");
|
||||
}
|
||||
File file = getMavenFile(libPath, groupId, artifactId, version, ext);
|
||||
if (!file.exists()) {
|
||||
downloadFile(file, groupId, artifactId, version, ext);
|
||||
}
|
||||
if (!new String(Files.readAllBytes(sha1.toPath())).equals(getSha1(file))) {
|
||||
sha1.delete();
|
||||
file.delete();
|
||||
throw new IllegalStateException("file " + file.getName() + " sha1 not match.");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public static File getMavenFile(String libPath, String groupId, String artifactId, String version, String ext) {
|
||||
return Paths.get(libPath, groupId.replace(".", File.separator), artifactId, version, String.format("%s-%s.%s", artifactId, version, ext)).toFile();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void downloadFile(File target, String groupId, String artifactId, String version, String ext) {
|
||||
target.getParentFile().mkdirs();
|
||||
URLConnection connection = new URL(MavenRepo +
|
||||
String.format("/%1$s/%2$s/%3$s/%2$s-%3$s.%4$s",
|
||||
groupId.replace(".", "/"),
|
||||
artifactId,
|
||||
version,
|
||||
ext)
|
||||
).openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(120000);
|
||||
connection.setUseCaches(true);
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
Files.copy(inputStream, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static String getSha1(File file) {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
|
||||
digest.update(byteBuffer);
|
||||
return getHash(digest.digest());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getHash(byte[] bytes) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
result.append(String.format("%02x", b));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package pw.yumc.MiaoScript.api.plugin;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import java.util.Map;
|
||||
|
||||
public interface PluginManager {
|
||||
Map<String, Bindings> getPlugins();
|
||||
|
||||
Bindings getPlugin(String name);
|
||||
|
||||
boolean has(String name);
|
||||
|
||||
Bindings get(String name);
|
||||
|
||||
boolean enable(String name);
|
||||
|
||||
boolean disable(String name);
|
||||
|
||||
boolean install(String name);
|
||||
|
||||
boolean uninstall(String name);
|
||||
}
|
268
src/main/java/pw/yumc/MiaoScript/engine/MiaoScriptEngine.java
Normal file
268
src/main/java/pw/yumc/MiaoScript/engine/MiaoScriptEngine.java
Normal file
@ -0,0 +1,268 @@
|
||||
package pw.yumc.MiaoScript.engine;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import pw.yumc.MiaoScript.api.loader.JarLoader;
|
||||
import pw.yumc.MiaoScript.api.loader.MavenDependLoader;
|
||||
|
||||
import javax.script.*;
|
||||
import java.io.File;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 喵式脚本引擎
|
||||
*
|
||||
* @author 喵♂呜
|
||||
* @since 2016年8月29日 上午7:51:43
|
||||
*/
|
||||
public class MiaoScriptEngine implements ScriptEngine, Invocable {
|
||||
private final String libsRoot;
|
||||
|
||||
@Getter
|
||||
private ScriptEngine engine;
|
||||
|
||||
@SneakyThrows
|
||||
public MiaoScriptEngine(String engineRoot) {
|
||||
File libRootFile = new File(engineRoot, "libs");
|
||||
libRootFile.mkdirs();
|
||||
this.libsRoot = libRootFile.getCanonicalPath();
|
||||
if (new File(engineRoot, "debug").exists()) {
|
||||
System.setProperty("nashorn.debug", "true");
|
||||
}
|
||||
MavenDependLoader.load(this.libsRoot, "org.kamranzafar", "jtar", "2.3");
|
||||
this.loadScriptEngine(engineRoot);
|
||||
if (this.engine == null)
|
||||
throw new UnsupportedOperationException("当前环境不支持 Nashorn 或 GraalJS 脚本引擎.");
|
||||
}
|
||||
|
||||
private void loadScriptEngine(String engineRoot) {
|
||||
if (new File(engineRoot, "graal").exists()) {
|
||||
this.engine = this.loadNetworkGraalJS();
|
||||
return;
|
||||
}
|
||||
if (getJavaVersion() > 15) {
|
||||
this.loadGraalJS();
|
||||
} else {
|
||||
this.loadNashorn();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGraalJS() {
|
||||
try {
|
||||
this.engine = this.parentLoadNetworkNashorn();
|
||||
} catch (Throwable ex) {
|
||||
this.engine = this.loadNetworkNashorn();
|
||||
}
|
||||
if (this.engine == null) {
|
||||
this.engine = this.loadNetworkGraalJS();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadNashorn() {
|
||||
try {
|
||||
this.createEngineByName();
|
||||
} catch (final Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
try {
|
||||
String extDirs = System.getProperty("java.ext.dirs");
|
||||
if (this.engine == null && extDirs != null) {
|
||||
this.engine = this.loadLocalNashorn(extDirs);
|
||||
}
|
||||
} catch (final Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
try {
|
||||
if (this.engine == null) {
|
||||
this.engine = this.loadNetworkNashorn();
|
||||
}
|
||||
} catch (final Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
if (this.engine == null)
|
||||
throw new UnsupportedOperationException("当前环境不支持 Nashorn 脚本引擎.");
|
||||
}
|
||||
|
||||
private int getJavaVersion() {
|
||||
String version = System.getProperty("java.version");
|
||||
if (version.startsWith("1.")) {
|
||||
version = version.substring(2, 3);
|
||||
} else {
|
||||
int dot = version.indexOf(".");
|
||||
if (dot != -1) {
|
||||
version = version.substring(0, dot);
|
||||
}
|
||||
}
|
||||
return Integer.parseInt(version);
|
||||
}
|
||||
|
||||
private ScriptEngine loadLocalNashorn(String extDirs) {
|
||||
String[] dirs = extDirs.split(File.pathSeparator);
|
||||
for (String dir : dirs) {
|
||||
File nashorn = new File(dir, "nashorn.jar");
|
||||
if (nashorn.exists()) {
|
||||
JarLoader.load(nashorn);
|
||||
return this.createEngineByName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String nashornVersion = "15.4";
|
||||
private String asmVersion = "9.6";
|
||||
|
||||
private ScriptEngine loadNetworkNashorn() {
|
||||
MavenDependLoader.load(this.libsRoot, "org.openjdk.nashorn", "nashorn-core", this.nashornVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.ow2.asm", "asm", this.asmVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.ow2.asm", "asm-commons", this.asmVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.ow2.asm", "asm-tree", this.asmVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.ow2.asm", "asm-util", this.asmVersion);
|
||||
return createEngineByFactoryClassName("org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory", false);
|
||||
}
|
||||
|
||||
private ScriptEngine parentLoadNetworkNashorn() {
|
||||
MavenDependLoader.parentLoad(this.libsRoot, "org.openjdk.nashorn", "nashorn-core", this.nashornVersion);
|
||||
MavenDependLoader.parentLoad(this.libsRoot, "org.ow2.asm", "asm", this.asmVersion);
|
||||
MavenDependLoader.parentLoad(this.libsRoot, "org.ow2.asm", "asm-commons", this.asmVersion);
|
||||
MavenDependLoader.parentLoad(this.libsRoot, "org.ow2.asm", "asm-tree", this.asmVersion);
|
||||
MavenDependLoader.parentLoad(this.libsRoot, "org.ow2.asm", "asm-util", this.asmVersion);
|
||||
return createEngineByFactoryClassName("org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory", false);
|
||||
}
|
||||
|
||||
private String graalVersion = "23.0.2";
|
||||
private String icu4jVersion = "72.1";
|
||||
|
||||
@SneakyThrows
|
||||
private ScriptEngine loadNetworkGraalJS() {
|
||||
MavenDependLoader.load(this.libsRoot, "org.graalvm.js", "js", this.graalVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "com.ibm.icu", "icu4j", this.icu4jVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.graalvm.js", "js-scriptengine", this.graalVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.graalvm.regex", "regex", this.graalVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.graalvm.sdk", "graal-sdk", this.graalVersion);
|
||||
MavenDependLoader.load(this.libsRoot, "org.graalvm.truffle", "truffle-api", this.graalVersion);
|
||||
System.setProperty("polyglot.engine.AllowExperimentalOptions", "true");
|
||||
System.setProperty("polyglot.engine.WarnInterpreterOnly", "false");
|
||||
System.setProperty("polyglot.js.nashorn-compat", "true");
|
||||
System.setProperty("polyglot.js.scripting", "true");
|
||||
System.setProperty("polyglot.js.ecmascript-version", "5");
|
||||
Class<?> NashornScriptEngineFactory = Class.forName("com.oracle.truffle.js.scriptengine.GraalJSEngineFactory");
|
||||
Method getScriptEngine = NashornScriptEngineFactory.getMethod("getScriptEngine");
|
||||
Object factory = NashornScriptEngineFactory.newInstance();
|
||||
ScriptEngine engine = (ScriptEngine) getScriptEngine.invoke(factory);
|
||||
Bindings bind = engine.getBindings(ScriptContext.ENGINE_SCOPE);
|
||||
bind.put("polyglot.js.allowAllAccess", true);
|
||||
return engine;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private ScriptEngine createEngineByName() {
|
||||
return createEngineByFactoryClassName("jdk.nashorn.api.scripting.NashornScriptEngineFactory", true);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private ScriptEngine createEngineByFactoryClassName(String factoryClassName, boolean jdk) {
|
||||
Class<?> NashornScriptEngineFactory = Class.forName(factoryClassName);
|
||||
Method getScriptEngine = NashornScriptEngineFactory.getMethod("getScriptEngine", String[].class);
|
||||
Object factory = NashornScriptEngineFactory.newInstance();
|
||||
List<String> engineArgs = new ArrayList<>();
|
||||
engineArgs.add("--language=es5");
|
||||
engineArgs.add("--optimistic-types=false");
|
||||
if (getJavaVersion() >= 11 && jdk) {
|
||||
engineArgs.add("--no-deprecation-warning");
|
||||
}
|
||||
return (ScriptEngine) getScriptEngine.invoke(factory, (Object) engineArgs.toArray(new String[] {}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bindings createBindings() {
|
||||
return engine.createBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final Reader reader) throws ScriptException {
|
||||
return engine.eval(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final Reader reader, final Bindings n) throws ScriptException {
|
||||
return engine.eval(reader, n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final Reader reader, final ScriptContext context) throws ScriptException {
|
||||
return engine.eval(reader, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final String script) throws ScriptException {
|
||||
return engine.eval(script);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final String script, final Bindings n) throws ScriptException {
|
||||
return engine.eval(script, n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(final String script, final ScriptContext context) throws ScriptException {
|
||||
return engine.eval(script, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(final String key) {
|
||||
return engine.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bindings getBindings(final int scope) {
|
||||
return engine.getBindings(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptContext getContext() {
|
||||
return engine.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptEngineFactory getFactory() {
|
||||
return engine.getFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getInterface(final Class<T> cls) {
|
||||
return ((Invocable) engine).getInterface(cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getInterface(final Object thiz, final Class<T> cls) {
|
||||
return ((Invocable) engine).getInterface(thiz, cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeFunction(final String name, final Object... args) throws ScriptException, NoSuchMethodException {
|
||||
return ((Invocable) engine).invokeFunction(name, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeMethod(final Object thiz, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
|
||||
return ((Invocable) engine).invokeMethod(thiz, name, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final String key, final Object value) {
|
||||
engine.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBindings(final Bindings bindings, final int scope) {
|
||||
engine.setBindings(bindings, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(final ScriptContext context) {
|
||||
engine.setContext(context);
|
||||
}
|
||||
}
|
@ -1,78 +1,97 @@
|
||||
'use strict';
|
||||
var log;
|
||||
var boot;
|
||||
var engineDisable;
|
||||
'use strict'
|
||||
var global = this;
|
||||
/**
|
||||
* Init MiaoScriptEngine Runtime
|
||||
*/
|
||||
/*global base ScriptEngineContextHolder*/
|
||||
(function () {
|
||||
var loader;
|
||||
boot = function(root, logger) {
|
||||
log = logger;
|
||||
var Files = Java.type('java.nio.file.Files')
|
||||
var Paths = Java.type('java.nio.file.Paths')
|
||||
var System = Java.type('java.lang.System')
|
||||
var Thread = Java.type('java.lang.Thread')
|
||||
var FutureTask = Java.type('java.util.concurrent.FutureTask')
|
||||
|
||||
global.boot = function (root, logger) {
|
||||
global.scope = System.getenv("MS_NODE_CORE_SCOPE") || "@ccms"
|
||||
global.logger = logger
|
||||
// Development Env Detect
|
||||
root = root || "src/main/resources";
|
||||
if (__FILE__ !== "<eval>") {
|
||||
logger.info('Loading custom BIOS file ' + __FILE__);
|
||||
global.debug = true;
|
||||
}
|
||||
if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "debug"))) {
|
||||
logger.info('Running in debug mode...');
|
||||
global.debug = true;
|
||||
}
|
||||
if (java.nio.file.Files.exists(java.nio.file.Paths.get(root, "level"))) {
|
||||
global.level = base.read(java.nio.file.Paths.get(root, "level"))
|
||||
logger.info('Set system level to [' + global.level + ']...');
|
||||
}
|
||||
// Check Class Loader, Sometimes Server will can't find plugin.yml file
|
||||
loader = checkClassLoader();
|
||||
// Force decompression core|node_modules to folder when not debug mode
|
||||
release(root, '(core)+/.*', !global.debug);
|
||||
// Async Loading MiaoScript Engine
|
||||
new java.lang.Thread(function() {
|
||||
load(root + '/core/ployfill.js')(root, logger);
|
||||
engineDisable = require('@ms/core').default;
|
||||
}, "MiaoScript thread").start()
|
||||
};
|
||||
|
||||
var pluginYml;
|
||||
function checkClassLoader() {
|
||||
var classLoader = java.lang.Thread.currentThread().contextClassLoader;
|
||||
pluginYml = classLoader.getResource("plugin.yml");
|
||||
if (pluginYml === null) {
|
||||
throw Error("Error class loader: " + classLoader.class.name + " Please contact the author MiaoWoo!");
|
||||
} else {
|
||||
log.info("Class loader compatible: " + classLoader.class.name);
|
||||
if (classLoader.parent) {
|
||||
log.info("Parent class loader: " + classLoader.parent.class.name);
|
||||
}
|
||||
}
|
||||
return classLoader;
|
||||
global.root = root || "src/main/resources"
|
||||
readEnvironment()
|
||||
if (!global.debug) { checkUpgrade() }
|
||||
return bootEngineThread(checkClassLoader())
|
||||
}
|
||||
|
||||
function release(root, regex, replace) {
|
||||
var filePath = pluginYml.getFile().substring(pluginYml.getFile().indexOf("/") + 1);
|
||||
var jarPath = java.net.URLDecoder.decode(filePath.substring(0, filePath.indexOf('!')));
|
||||
if (!java.nio.file.Files.exists(java.nio.file.Paths.get(jarPath))) {
|
||||
jarPath = "/" + jarPath;
|
||||
}
|
||||
var jar = new java.util.jar.JarFile(jarPath);
|
||||
var r = new RegExp(regex);
|
||||
jar.stream().forEach(function(entry) {
|
||||
try {
|
||||
if (!entry.isDirectory()) {
|
||||
if (r.test(entry.name)) {
|
||||
var path = java.nio.file.Paths.get(root, entry.name);
|
||||
var parentFile = path.toFile().parentFile;
|
||||
if (!parentFile.exists()) { parentFile.mkdirs(); }
|
||||
if (!java.nio.file.Files.exists(path) || replace) {
|
||||
java.nio.file.Files.copy(loader.getResourceAsStream(entry.name), path, java.nio.file.StandardCopyOption['REPLACE_EXISTING']);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
function bootEngineThread(loader) {
|
||||
logger.info("ScriptEngine: " + ScriptEngineContextHolder.getEngine().getEngine().class.name)
|
||||
var future = new FutureTask(function () {
|
||||
Thread.currentThread().contextClassLoader = loader
|
||||
load(System.getenv("MS_NODE_CORE_POLYFILL") || 'classpath:core/polyfill.js')(root, logger)
|
||||
var core = require(System.getenv("MS_NODE_CORE_MODULE") || (global.scope + '/core'))
|
||||
return core.default || core
|
||||
})
|
||||
// Async Loading MiaoScript Engine
|
||||
new Thread(future, "MiaoScript thread").start()
|
||||
return future
|
||||
}
|
||||
})();
|
||||
|
||||
global.enable = function (future) {
|
||||
// await polyfill loading
|
||||
if (!future.isDone()) { logger.info("MiaoScript booting...") }
|
||||
// faster load core
|
||||
var core = future.get()
|
||||
logger.info("MiaoScript starting...")
|
||||
global.engineDisableImpl = core.enable()
|
||||
}
|
||||
|
||||
global.disable = function () {
|
||||
(global.engineDisableImpl || function () {
|
||||
logger.info('Error: abnormal Initialization MiaoScript Engine. Skip disable step...')
|
||||
})()
|
||||
}
|
||||
|
||||
function readEnvironment() {
|
||||
if (__FILE__.indexOf('!') === -1) {
|
||||
logger.info('loading custom BIOS file ' + __FILE__)
|
||||
global.debug = true
|
||||
}
|
||||
if (Files.exists(Paths.get(root, "debug"))) {
|
||||
logger.info('running in debug mode...')
|
||||
global.debug = true
|
||||
}
|
||||
if (Files.exists(Paths.get(root, "level"))) {
|
||||
global.ScriptEngineLoggerLevel = base.read(Paths.get(root, "level"))
|
||||
logger.info('found level set ScriptEngineLoggerLevel to ' + global.ScriptEngineLoggerLevel + '.')
|
||||
}
|
||||
if (Files.exists(Paths.get(root, "channel"))) {
|
||||
global.ScriptEngineChannel = base.read(Paths.get(root, "channel"))
|
||||
logger.info('found channel set ScriptEngineChannel to ' + global.ScriptEngineChannel + '.')
|
||||
}
|
||||
}
|
||||
|
||||
function checkUpgrade() {
|
||||
if (Files.exists(Paths.get(root, "upgrade"))) {
|
||||
logger.info('found upgrade file starting upgrade...')
|
||||
base.move(Paths.get(root, "node_modules"), Paths.get(root, "old_node_modules"))
|
||||
base.delete(Paths.get(root, "upgrade"))
|
||||
}
|
||||
new Thread(function () {
|
||||
try {
|
||||
base.delete(Paths.get(root, "old_node_modules"))
|
||||
} catch (ex) {
|
||||
}
|
||||
}, "MiaoScript node_modules clean thread").start()
|
||||
}
|
||||
|
||||
function checkClassLoader() {
|
||||
// Check Class Loader, Sometimes Server will can't found plugin.yml file
|
||||
var classLoader = Thread.currentThread().contextClassLoader
|
||||
if (classLoader.getResource("bios.js") === null) {
|
||||
throw Error("Error class loader: " + classLoader.class.name + " Please contact the author MiaoWoo!")
|
||||
}
|
||||
logger.info("Class loader compatible: " + classLoader.class.name)
|
||||
if (classLoader.parent) {
|
||||
logger.info("Parent class loader: " + classLoader.parent.class.name)
|
||||
}
|
||||
return classLoader
|
||||
}
|
||||
})()
|
||||
|
@ -1,12 +1,31 @@
|
||||
/// <reference path="./index.d.ts" />
|
||||
// @ts-check
|
||||
(
|
||||
/**
|
||||
* @param {{ info: (arg0: string) => void; }} logger
|
||||
* @param {{
|
||||
* info: (arg0: string) => void;
|
||||
* warn: (arg0: string) => void;
|
||||
* debug: (arg0: string) => void;
|
||||
* error: (arg0: string) => void;
|
||||
* warning: (arg0: string) => void;
|
||||
* }} logger
|
||||
*/
|
||||
function (logger) {
|
||||
function log() {
|
||||
logger.info(Array.prototype.join.call(arguments, ' '))
|
||||
}
|
||||
function warn() {
|
||||
logger.warn(Array.prototype.join.call(arguments, ' '))
|
||||
}
|
||||
function debug() {
|
||||
logger.debug(Array.prototype.join.call(arguments, ' '))
|
||||
}
|
||||
function error() {
|
||||
logger.error(Array.prototype.join.call(arguments, ' '))
|
||||
}
|
||||
function warning() {
|
||||
logger.warning(Array.prototype.join.call(arguments, ' '))
|
||||
}
|
||||
/**
|
||||
* @param {string} prefix
|
||||
*/
|
||||
@ -15,15 +34,23 @@
|
||||
log('[' + prefix + ']', Array.prototype.join.call(arguments, ' '))
|
||||
}
|
||||
}
|
||||
return {
|
||||
var logProxy = {
|
||||
log: log,
|
||||
info: log,
|
||||
ex: log,
|
||||
// @ts-ignore
|
||||
trace: global.level === "trace" ? _proxy('TRACE') : global.noop,
|
||||
// @ts-ignore
|
||||
debug: global.debug ? _proxy('DEBUG') : global.noop,
|
||||
trace: global.ScriptEngineLoggerLevel === "trace" ? _proxy('TRACE') : global.noop,
|
||||
debug: global.debug ? logger.debug ? debug : _proxy('DEBUG') : global.noop,
|
||||
warn: _proxy('WARN'),
|
||||
error: _proxy('ERROR')
|
||||
};
|
||||
}
|
||||
if (logger.warn) {
|
||||
logProxy.warn = warn
|
||||
}
|
||||
if (logger.warning) {
|
||||
logProxy.warn = warning
|
||||
}
|
||||
if (logger.error) {
|
||||
logProxy.error = error
|
||||
}
|
||||
return logProxy
|
||||
})
|
||||
|
25
src/main/resources/core/index.d.ts
vendored
Normal file
25
src/main/resources/core/index.d.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
declare const global: any
|
||||
declare const root: string
|
||||
declare const base: Core
|
||||
declare function engineLoad(str: string | { script: string, name: string }): any
|
||||
interface Core {
|
||||
version: string
|
||||
getClass(name: String): any
|
||||
getProxyClass(): any
|
||||
getJavaScriptTaskClass(): any
|
||||
getInstance(): any
|
||||
read(path: string): string
|
||||
save(path: string, content: string): void
|
||||
delete(path: string): void
|
||||
}
|
||||
namespace Java {
|
||||
function type<T = any>(clazz: string): T
|
||||
function from<T = any>(javaObj: T[]): T[]
|
||||
function to<T = any>(array: T[], type?: T): T[]
|
||||
function extend(...parentTypes: any[]): any
|
||||
function synchronized(func: () => void, lock: any): Function
|
||||
function isJavaObject(obj: any): boolean
|
||||
function asJSONCompatible<T = any>(obj: T): T
|
||||
//@ts-ignore
|
||||
// function super(type: any);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
(
|
||||
/**
|
||||
* @param {string} root
|
||||
* @param {any} logger
|
||||
*/
|
||||
function(root, logger) {
|
||||
// Init Global Value
|
||||
global.root = root;
|
||||
global.logger = logger;
|
||||
global.NashornEngineStartTime = new Date().getTime()
|
||||
global.engineLoad = load;
|
||||
global.noop = global.engineDisable = engineDisable = function() { };
|
||||
global.load = load = function __PreventGlobalLoadFunction__() { throw new Error('Internal engine system not allow use `load` function!'); }
|
||||
global.setGlobal = function(key, value) { global[key] = value; };
|
||||
// Init console and require
|
||||
global.console = engineLoad(global.root + '/core/console.js')(logger);
|
||||
console.log("Loading Engine at Thread", java.lang.Thread.currentThread().name)
|
||||
global.require = engineLoad(global.root + '/core/require.js')(root);
|
||||
require('@ms/ployfill')
|
||||
}
|
||||
);
|
32
src/main/resources/core/polyfill.js
Normal file
32
src/main/resources/core/polyfill.js
Normal file
@ -0,0 +1,32 @@
|
||||
/// <reference path="./index.d.ts" />
|
||||
(
|
||||
/**
|
||||
* @param {string} root
|
||||
* @param {any} logger
|
||||
*/
|
||||
function (root, logger) {
|
||||
var System = Java.type('java.lang.System')
|
||||
var Thread = Java.type('java.lang.Thread')
|
||||
|
||||
// Init Global Value
|
||||
global.root = root
|
||||
global.logger = logger
|
||||
global.ScriptEngineStartTime = new Date().getTime()
|
||||
global.engineLoad = load
|
||||
global.noop = function () { }
|
||||
global.load = load = function __PreventGlobalLoadFunction__() { throw new Error('Internal engine system not allow use `load` function!') }
|
||||
global.setGlobal = function (key, value, config) {
|
||||
if (config) {
|
||||
config.value = value
|
||||
Object.defineProperty(global, key, config)
|
||||
} else {
|
||||
global[key] = value
|
||||
}
|
||||
}
|
||||
// Init console and require
|
||||
global.console = engineLoad(System.getenv("MS_NODE_CORE_CONSOLE") || 'classpath:core/console.js')(logger)
|
||||
console.log("Loading Engine at Thread", Thread.currentThread().name)
|
||||
global.require = engineLoad(System.getenv("MS_NODE_CORE_REQUIRE") || 'classpath:core/require.js')(root)
|
||||
return require(global.scope + '/polyfill')
|
||||
}
|
||||
)
|
@ -24,50 +24,77 @@
|
||||
* 3. 如果 xx/index.json 存在 则使用 `xx/index.json` 解析为对象加载 并停止执行
|
||||
* 暂不支持 4. 如果 xx/index.msm 是一个文件 则使用MScript解析器解析 并停止执行
|
||||
*/
|
||||
/// <reference path="./index.d.ts" />
|
||||
// @ts-check
|
||||
/// <reference types="@ms/nashorn" />
|
||||
(
|
||||
/**
|
||||
* @param {any} parent
|
||||
* @param {string} root
|
||||
*/
|
||||
function(parent) {
|
||||
'use strict';
|
||||
var File = Java.type('java.io.File');
|
||||
var Paths = Java.type('java.nio.file.Paths');
|
||||
var Files = Java.type('java.nio.file.Files');
|
||||
var StandardCopyOption = Java.type('java.nio.file.StandardCopyOption');
|
||||
var FileNotFoundException = Java.type('java.io.FileNotFoundException');
|
||||
function (root) {
|
||||
'use strict'
|
||||
var System = Java.type('java.lang.System')
|
||||
|
||||
var TarInputStream = Java.type('org.kamranzafar.jtar.TarInputStream');
|
||||
var GZIPInputStream = Java.type('java.util.zip.GZIPInputStream');
|
||||
var BufferedInputStream = Java.type('java.io.BufferedInputStream');
|
||||
var File = Java.type('java.io.File')
|
||||
var Paths = Java.type('java.nio.file.Paths')
|
||||
var Files = Java.type('java.nio.file.Files')
|
||||
var StandardCopyOption = Java.type('java.nio.file.StandardCopyOption')
|
||||
|
||||
var TarInputStream = Java.type('org.kamranzafar.jtar.TarInputStream')
|
||||
var GZIPInputStream = Java.type('java.util.zip.GZIPInputStream')
|
||||
var BufferedInputStream = Java.type('java.io.BufferedInputStream')
|
||||
|
||||
var URL = Java.type('java.net.URL')
|
||||
var JavaString = Java.type('java.lang.String')
|
||||
var separatorChar = File.separatorChar;
|
||||
var ByteArrayOutputStream = Java.type("java.io.ByteArrayOutputStream")
|
||||
var ByteArray = Java.type("byte[]")
|
||||
var Thread = Java.type('java.lang.Thread')
|
||||
var Callable = Java.type('java.util.concurrent.Callable')
|
||||
var Executors = Java.type('java.util.concurrent.Executors')
|
||||
var TimeUnit = Java.type('java.util.concurrent.TimeUnit')
|
||||
var separatorChar = File.separatorChar
|
||||
|
||||
var CoreModules = ['assert', 'async_hooks', 'child_process', 'cluster', 'crypto', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'inspector', 'net', 'os', 'path', 'vm', 'url', 'util', 'zlib', 'worker_threads']
|
||||
var MS_NODE_PATH = System.getenv("MS_NODE_PATH") || root + separatorChar + 'node_modules'
|
||||
var MS_NODE_REGISTRY = System.getenv("MS_NODE_REGISTRY") || 'https://registry.npmmirror.com'
|
||||
var MS_FALLBACK_NODE_REGISTRY = System.getenv("MS_FALLBACK_NODE_REGISTRY") || 'https://repo.yumc.pw/repository/npm'
|
||||
var MS_SCRIPT_PACKAGE_CENTER = System.getenv("MS_SCRIPT_PACKAGE_CENTER") || 'https://mscript.yumc.pw/api/plugin/download'
|
||||
var MS_NETWORK_CONNECT_TIMEOUT = System.getenv("MS_NETWORK_CONNECT_TIMEOUT") || 5000
|
||||
var MS_NETWORK_READ_TIMEOUT = System.getenv("MS_NETWORK_TIMEOUT") || 45000
|
||||
var MS_NETWORK_DOWNLOAD_TIMEOUT = System.getenv("MS_NETWORK_DOWNLOAD_TIMEOUT") || 60000
|
||||
var MS_NETWORK_USE_CACHES = System.getenv("MS_NETWORK_USE_CACHES") || true
|
||||
|
||||
var CoreModules = [
|
||||
"assert", "async_hooks", "Buffer", "child_process", "cluster", "crypto",
|
||||
"dgram", "dns", "domain", "events", "fs", "http", "http2", "https",
|
||||
"inspector", "net", "os", "path", "perf_hooks", "process", "punycode",
|
||||
"querystring", "readline", "repl", "stream", "string_decoder",
|
||||
"timer", "tls", "trace_events", "tty", "url", "util",
|
||||
"v8", "vm", "wasi", "worker_threads", "zlib"
|
||||
]
|
||||
|
||||
var VersionLockModules = {}
|
||||
|
||||
/**
|
||||
* @param {...object} t
|
||||
*/
|
||||
function __assign(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
if (s === undefined) { continue; };
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
s = arguments[i]
|
||||
if (s === undefined) {
|
||||
continue
|
||||
}
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p]
|
||||
}
|
||||
return t
|
||||
}
|
||||
return t;
|
||||
};
|
||||
|
||||
// noinspection JSValidateJSDoc
|
||||
/**
|
||||
* 判断是否为一个文件
|
||||
* @param {any} file
|
||||
* @returns {*}
|
||||
*/
|
||||
function _isFile(file) {
|
||||
return file.isFile && file.isFile();
|
||||
return file && file.isFile && file.isFile()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,16 +103,14 @@
|
||||
* @returns {*}
|
||||
*/
|
||||
function _canonical(file) {
|
||||
return file.canonicalPath;
|
||||
return file.canonicalPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得文件绝对路径
|
||||
* @param {any} file
|
||||
* @returns {*}
|
||||
*/
|
||||
function _absolute(file) {
|
||||
return file.absolutePath;
|
||||
function __error(message, name) {
|
||||
var error = new Error(message)
|
||||
if (name) { error.name = name }
|
||||
console.error(message)
|
||||
return error
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,48 +118,51 @@
|
||||
* 按照下列顺序查找
|
||||
* 当前目录 ./
|
||||
* 父目录 ../
|
||||
* 模块目录 /node_modules
|
||||
* 递归模块目录 ../node_modules 到root
|
||||
* 寻找 ${NODE_PATH}
|
||||
* @param {string} name 模块名称
|
||||
* @param {string} parent 父目录
|
||||
* @param {any} optional 附加参数
|
||||
*/
|
||||
function resolve(name, parent) {
|
||||
name = _canonical(name) || name;
|
||||
function resolve(name, parent, optional) {
|
||||
name = _canonical(name) || name
|
||||
// 解析本地目录
|
||||
if (name.startsWith('./') || name.startsWith('../')) {
|
||||
return resolveAsFile(name, parent) || resolveAsDirectory(name, parent) || undefined;
|
||||
if (optional.local) {
|
||||
return resolveAsFile(name, parent) || resolveAsDirectory(name, parent) || undefined
|
||||
} else {
|
||||
// 解析 root 模块目录
|
||||
var rootModule = resolveAsFile(name, MS_NODE_PATH) || resolveAsDirectory(name, MS_NODE_PATH)
|
||||
if (rootModule) { return rootModule }
|
||||
// 解析Node目录
|
||||
var dir = [parent, 'node_modules'].join(separatorChar);
|
||||
if (cacheModuleIds[name]) return cacheModuleIds[name]
|
||||
cacheModuleIds[name] = resolveAsFile(name, dir) || resolveAsDirectory(name, dir) ||
|
||||
// @ts-ignore
|
||||
(parent && parent.toString().startsWith(root) ? resolve(name, new File(parent).getParent()) : undefined);
|
||||
return cacheModuleIds[name];
|
||||
var dir = [parent, 'node_modules'].join(separatorChar)
|
||||
return resolveAsFile(name, dir) || resolveAsDirectory(name, dir) ||
|
||||
(parent && parent.toString().startsWith(root) ?
|
||||
resolve(name, new File(parent).getParent(), optional) : undefined)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析文件
|
||||
* @param {string} file 文件
|
||||
* @param {any} file 文件
|
||||
* @param {string | undefined} dir 目录
|
||||
* @returns {*}
|
||||
*/
|
||||
function resolveAsFile(file, dir) {
|
||||
file = dir != undefined ? new File(dir, file) : new File(file);
|
||||
file = dir !== undefined ? new File(dir, file) : new File(file)
|
||||
// 直接文件
|
||||
// @ts-ignore
|
||||
if (file.isFile()) {
|
||||
return file;
|
||||
// 只解析带后缀的文件 其他文件视为非法文件
|
||||
if (file.isFile() && file.name.lastIndexOf('.') != -1) {
|
||||
return file
|
||||
}
|
||||
// JS文件
|
||||
var js = new File(normalizeName(_absolute(file), '.js'));
|
||||
var js = new File(normalizeName(_canonical(file), '.js'))
|
||||
if (js.isFile()) {
|
||||
return js;
|
||||
return js
|
||||
}
|
||||
// JSON文件
|
||||
var json = new File(normalizeName(_absolute(file), '.json'));
|
||||
var json = new File(normalizeName(_canonical(file), '.json'))
|
||||
if (json.isFile()) {
|
||||
return json;
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,17 +173,20 @@
|
||||
* @returns {*}
|
||||
*/
|
||||
function resolveAsDirectory(file, dir) {
|
||||
dir = dir != undefined ? new File(dir, file) : new File(file);
|
||||
var _package = new File(dir, 'package.json');
|
||||
dir = dir !== undefined ? new File(dir, file) : new File(file)
|
||||
var _package = new File(dir, 'package.json')
|
||||
if (_package.exists()) {
|
||||
// @ts-ignore
|
||||
var json = JSON.parse(base.read(_package));
|
||||
try {
|
||||
var json = JSON.parse(base.read(_package))
|
||||
if (json.main) {
|
||||
return resolveAsFile(json.main, dir);
|
||||
return resolveAsFile(json.main, dir) || resolveAsFile('index.js', new File(dir, json.main))
|
||||
}
|
||||
} catch (error) {
|
||||
throw __error('resolveAsDirectory ' + dir + ' package.json error ' + error)
|
||||
}
|
||||
}
|
||||
// if no package or package.main exists, look for index.js
|
||||
return resolveAsFile('index.js', dir);
|
||||
return resolveAsFile('index.js', dir)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,57 +196,69 @@
|
||||
* @returns {*}
|
||||
*/
|
||||
function normalizeName(fileName, ext) {
|
||||
var extension = ext || '.js';
|
||||
var extension = ext || '.js'
|
||||
if (fileName.endsWith(extension)) {
|
||||
return fileName;
|
||||
return fileName
|
||||
}
|
||||
return fileName + extension;
|
||||
return fileName + extension
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块缓存
|
||||
* @param {string} id 模块ID
|
||||
* @param {string} name 模块名称
|
||||
* @param {any} file 模块文件
|
||||
* @param {any} optional 附加选项
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getCacheModule(id, name, file, optional) {
|
||||
var module = cacheModules[id];
|
||||
function getCacheModule(id, file, optional) {
|
||||
var module = cacheModules[id]
|
||||
if (optional.cache && module) {
|
||||
return module;
|
||||
return module
|
||||
}
|
||||
return createModule(id, name, file, optional)
|
||||
return createModule(id, file, optional)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译模块
|
||||
* @param {string} id 模块ID
|
||||
* @param {string} name 模块名称
|
||||
* @param {any} file 模块文件
|
||||
* @param {any} optional 附加选项
|
||||
* @returns {Object}
|
||||
*/
|
||||
function createModule(id, name, file, optional) {
|
||||
console.trace('Loading module', name + '(' + id + ')', 'Optional', JSON.stringify(optional));
|
||||
function createModule(id, file, optional) {
|
||||
var filename = file.name
|
||||
var lastDotIndexOf = filename.lastIndexOf('.')
|
||||
if (lastDotIndexOf == -1) {
|
||||
throw __error("can't require file " + file + '. error: module must include file ext.')
|
||||
}
|
||||
var name = filename.substring(0, lastDotIndexOf)
|
||||
var ext = filename.substring(lastDotIndexOf + 1)
|
||||
var loader = requireLoaders[ext]
|
||||
if (!loader) {
|
||||
throw __error('Unsupported module ' + filename + '. require loader not found.')
|
||||
}
|
||||
/**
|
||||
* @type any
|
||||
*/
|
||||
var module = {
|
||||
id: id,
|
||||
name: name,
|
||||
ext: ext,
|
||||
parent: optional.parent,
|
||||
exports: {},
|
||||
loaded: false,
|
||||
require: getRequire(file.parentFile, id)
|
||||
};
|
||||
cacheModules[id] = module;
|
||||
var cfile = _canonical(file);
|
||||
if (cfile.endsWith('.js')) {
|
||||
compileJs(module, file, optional);
|
||||
} else if (cfile.endsWith('.json')) {
|
||||
compileJson(module, file);
|
||||
} else if (cfile.endsWith('.msm')) {
|
||||
throw Error('Unsupported MiaoScript module!');
|
||||
} else {
|
||||
throw Error('Unknown file type ' + cfile);
|
||||
loader: loader,
|
||||
path: _canonical(file.parentFile),
|
||||
filename: _canonical(file),
|
||||
children: []
|
||||
}
|
||||
return module;
|
||||
module.require = getRequire(module)
|
||||
if (module.parent && module.parent.children && module.parent.children.indexOf(module) == -1) {
|
||||
module.parent.children.push(module)
|
||||
}
|
||||
console.trace('Loading module', name + '(' + id + ')', 'Optional', JSON.stringify(__assign(optional, { parent: undefined })))
|
||||
cacheModules[id] = module
|
||||
return loader(module, file, __assign(optional, { id: id }))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,79 +266,216 @@
|
||||
* @param {any} module JS模块
|
||||
* @param {any} file JS文件
|
||||
* @param {any} optional 附加选项
|
||||
* @returns {void}
|
||||
* @returns {any}
|
||||
*/
|
||||
function compileJs(module, file, optional) {
|
||||
// @ts-ignore
|
||||
var origin = base.read(file);
|
||||
if (optional.hook) {
|
||||
origin = optional.hook(origin);
|
||||
function compileJsFile(module, file, optional) {
|
||||
return compileJs(module, base.read(file), optional)
|
||||
}
|
||||
|
||||
/**
|
||||
* 预编译JS
|
||||
* @param {any} module JS模块
|
||||
* @param {any} script JS脚本
|
||||
* @param {any} optional 附加选项
|
||||
* @returns {any}
|
||||
*/
|
||||
function compileJs(module, script, optional) {
|
||||
if (optional.beforeCompile) {
|
||||
script = optional.beforeCompile(script)
|
||||
}
|
||||
// 2019-09-19 使用 扩展函数直接 load 无需保存/删除文件
|
||||
// 2020-02-16 结尾新增换行 防止有注释导致加载失败
|
||||
// @ts-ignore
|
||||
var compiledWrapper = engineLoad({ script: '(function $(module, exports, require, __dirname, __filename) {' + origin + '\n});', name: file });
|
||||
var wrapperScript = '(function (module, exports, require, __dirname, __filename) {' + script + '\n});'
|
||||
var compiledWrapper = engineLoad({
|
||||
script: wrapperScript,
|
||||
name: optional.id
|
||||
})
|
||||
compiledWrapper.apply(module.exports, [
|
||||
module, module.exports, module.require, file.parentFile, file
|
||||
]);
|
||||
module.loaded = true;
|
||||
module, module.exports, module.require, module.path, module.filename
|
||||
])
|
||||
module.loaded = true
|
||||
if (optional.afterCompile) {
|
||||
module = optional.afterCompile(module) || module
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
/**
|
||||
* 预编译Json
|
||||
* @param {{ id?: string | null; exports?: {}; loaded: any; require?: any; }} module Json模块
|
||||
* @param {any} file Json 文件
|
||||
* @returns {void}
|
||||
* @returns {any}
|
||||
*/
|
||||
function compileJson(module, file) {
|
||||
// @ts-ignore
|
||||
module.exports = JSON.parse(base.read(file));
|
||||
module.loaded = true;
|
||||
module.exports = JSON.parse(base.read(file))
|
||||
module.loaded = true
|
||||
return module
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得网络链接
|
||||
* @param {string} url 网址
|
||||
*/
|
||||
function getConnection(url) {
|
||||
var connection = new URL(url).openConnection()
|
||||
connection.setConnectTimeout(MS_NETWORK_CONNECT_TIMEOUT)
|
||||
connection.setReadTimeout(MS_NETWORK_READ_TIMEOUT)
|
||||
connection.setUseCaches(MS_NETWORK_USE_CACHES)
|
||||
return connection
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得网络流
|
||||
* @param {string} url 网址
|
||||
*/
|
||||
function getConnectionStream(url) {
|
||||
var connection = getConnection(url)
|
||||
return connection.getInputStream()
|
||||
}
|
||||
|
||||
function splitVersionFromName(name) {
|
||||
// process package name
|
||||
// es6-map/implement => [es6-map/implement, undefined]
|
||||
// @ccms/common/dist/reflect => [@ccms/common, undefined]
|
||||
var name_arr = name.split('/')
|
||||
var module_name = ''
|
||||
var module_version = ''
|
||||
if (name.startsWith('@')) {
|
||||
var module_version_arr = name_arr[1].split('@')
|
||||
module_name = name_arr[0] + '/' + module_version_arr[0]
|
||||
} else {
|
||||
var module_version_arr = name_arr[0].split('@')
|
||||
module_name = module_version_arr[0]
|
||||
}
|
||||
// handle internal package version
|
||||
if (name.startsWith(global.scope) && global.ScriptEngineChannel) {
|
||||
module_version = global.ScriptEngineChannel
|
||||
} else {
|
||||
module_version = module_version_arr[1]
|
||||
}
|
||||
return [module_name, module_version]
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试从网络下载依赖包
|
||||
* @param {string} name 包名称
|
||||
* @param {string} optional 附加选项
|
||||
* @param {number} retry 重试次数
|
||||
*/
|
||||
function download(name) {
|
||||
// handle name es6-map/implement => es6-map @ms/common/dist/reflect => @ms/common
|
||||
var name_arr = name.split('/');
|
||||
var module_name = name.startsWith('@') ? name_arr[0] + '/' + name_arr[1] : name_arr[0];
|
||||
// at windows need replace file name java.lang.IllegalArgumentException: Invalid prefix or suffix
|
||||
var tempFile = Files.createTempFile(module_name.replace('/', '_'), '.json');
|
||||
var info = fetchPackageInfo(module_name, tempFile);
|
||||
var url = info.versions[info['dist-tags']['latest']].dist.tarball;
|
||||
console.log('fetch node_module ' + module_name + ' from ' + url + ' waiting...')
|
||||
var tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new URL(url).openStream())));
|
||||
// @ts-ignore
|
||||
var entry; var target = root + separatorChar + 'node_modules' + separatorChar + module_name;
|
||||
while ((entry = tis.getNextEntry()) != null) {
|
||||
var targetPath = Paths.get(target + separatorChar + entry.getName().substring(8));
|
||||
targetPath.toFile().getParentFile().mkdirs();
|
||||
Files.copy(tis, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
function download(name, optional, retry) {
|
||||
var name_arr = splitVersionFromName(name)
|
||||
var module_name = name_arr[0]
|
||||
var module_version = name_arr[1]
|
||||
try {
|
||||
var target = MS_NODE_PATH + separatorChar + module_name
|
||||
if (new File(target, 'package.json').exists()) { return name }
|
||||
var info = fetchPackageInfo(module_name)
|
||||
if (!module_version) {
|
||||
// if not special version get from lock or tag
|
||||
module_version = VersionLockModules[module_name]
|
||||
} else if (!/\d+\.\d+\.\w+/.test(module_version)) {
|
||||
// maybe module_version = latest if special version not exist then fallback latest
|
||||
console.log('try get node_module ' + module_name + ' version from ' + module_version + ' tag waiting...')
|
||||
module_version = info['dist-tags'][module_version]
|
||||
}
|
||||
if (!module_version) {
|
||||
console.log('try get node_module ' + module_name + ' version from latest tag waiting...')
|
||||
module_version = info['dist-tags']['latest']
|
||||
}
|
||||
if (!module_version) { throw __error('fetch node_module ' + module_name + " failed. can't found version from " + name + ".", 'ModuleNotFoundError') }
|
||||
var _version = info.versions[module_version]
|
||||
if (!_version) { throw __error('fetch node_module ' + module_name + ' version ' + module_version + " failed. can't found tarball from versions.", 'ModuleNotFoundError') }
|
||||
var url = _version.dist.tarball
|
||||
console.log('fetch node_module ' + module_name + ' version ' + module_version + ' waiting...')
|
||||
return executor.submit(new Callable(function () {
|
||||
var tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(getConnectionStream(url))))
|
||||
var entry
|
||||
while ((entry = tis.getNextEntry()) != null) {
|
||||
var targetPath = Paths.get(target + separatorChar + entry.getName().substring(8))
|
||||
var parentFile = targetPath.toFile().getParentFile()
|
||||
if (!parentFile.isDirectory()) {
|
||||
parentFile.delete()
|
||||
parentFile.mkdirs()
|
||||
}
|
||||
Files.copy(tis, targetPath, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
return name
|
||||
})).get(MS_NETWORK_DOWNLOAD_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
} catch (error) {
|
||||
if (error.name == 'ModuleNotFoundError') { throw error }
|
||||
if (retry > 3) { throw __error('fetch node_module ' + module_name + ' version ' + module_version + ' failed. greater than 3 times stop retry.') }
|
||||
console.log('fetch node_module ' + module_name + ' version ' + module_version + ' failed retrying...')
|
||||
return download(name, optional, ++retry)
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function fetchPackageInfo(module_name, tempFile) {
|
||||
/**
|
||||
* 获取包信息
|
||||
* @param {string} module_name
|
||||
*/
|
||||
function fetchPackageInfo(module_name) {
|
||||
var content = ''
|
||||
try {
|
||||
Files.copy(new URL('https://registry.npm.taobao.org/' + module_name).openStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
content = fetchContent(MS_NODE_REGISTRY + '/' + module_name)
|
||||
} catch (ex) {
|
||||
console.debug('can\'t fetch package ' + module_name + ' from taobao registry. try fetch from yumc registry...')
|
||||
Files.copy(new URL('https://repo.yumc.pw/repository/npm/' + module_name).openStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
console.warn("can't fetch package " + module_name + ' from ' + MS_NODE_REGISTRY + ' registry. try fetch from ' + MS_FALLBACK_NODE_REGISTRY + ' registry...')
|
||||
content = fetchContent(MS_FALLBACK_NODE_REGISTRY + '/' + module_name)
|
||||
}
|
||||
tempFile.toFile().deleteOnExit();
|
||||
return JSON.parse(new JavaString(Files.readAllBytes(tempFile), 'UTF-8'));
|
||||
return JSON.parse(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网络内容
|
||||
* @param {string} url 网址
|
||||
* @param {number} [timeout] 超时时间
|
||||
*/
|
||||
function fetchContent(url, timeout) {
|
||||
return executor.submit(new Callable(function fetchContent() {
|
||||
var input = getConnectionStream(url)
|
||||
var output = new ByteArrayOutputStream()
|
||||
var buffer = new ByteArray(1024)
|
||||
try {
|
||||
var n
|
||||
while ((n = input.read(buffer)) !== -1) {
|
||||
output.write(buffer, 0, n)
|
||||
}
|
||||
return output.toString("UTF-8")
|
||||
} finally {
|
||||
input.close()
|
||||
output.close()
|
||||
}
|
||||
})).get(timeout || MS_NETWORK_READ_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
var lastModule = ''
|
||||
|
||||
/**
|
||||
* 检查核心模块
|
||||
* @param {string} name
|
||||
* @param {string} path
|
||||
*/
|
||||
function checkCoreModule(name) {
|
||||
if (CoreModules.indexOf(name) != -1) {
|
||||
throw new Error("Can't load nodejs core module " + name + " . maybe later will auto replace to @ms/" + name + ' to compatible...')
|
||||
function checkCoreModule(name, path, optional) {
|
||||
if (name.startsWith('@ms') && lastModule.endsWith('.js')) {
|
||||
console.warn(lastModule + ' load deprecated module ' + name + ' auto replace to ' + (name = name.replace('@ms', global.scope)) + '...')
|
||||
return name
|
||||
} else {
|
||||
lastModule = name
|
||||
}
|
||||
if (CoreModules.indexOf(name) !== -1) {
|
||||
var newName = global.scope + '/nodejs/dist/' + name
|
||||
if (resolve(newName, path, optional) !== undefined) {
|
||||
return newName
|
||||
}
|
||||
throw __error("can't load nodejs core module " + name + " . maybe later will auto replace to " + global.scope + "/nodejs/" + name + ' to compatible...')
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存模块
|
||||
*/
|
||||
function checkCacheModule(optional) {
|
||||
return optional.local ? cacheModuleIds[optional.parent.id] && cacheModuleIds[optional.parent.id][optional.path] : cacheModuleIds[optional.path]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -306,89 +486,286 @@
|
||||
* @returns {*}
|
||||
*/
|
||||
function _require(name, path, optional) {
|
||||
checkCoreModule(name);
|
||||
var file = new File(name);
|
||||
file = _isFile(file) ? file : resolve(name, path);
|
||||
optional = __assign({ cache: true }, optional);
|
||||
// require direct file
|
||||
var file = _isFile(name) ? name : new File(name)
|
||||
if (_isFile(file) && file.name.lastIndexOf('.') != -1) {
|
||||
return _requireFile(file, optional)
|
||||
}
|
||||
// require cache module
|
||||
var cachePath = checkCacheModule(optional)
|
||||
var cacheFile = new File(cachePath)
|
||||
if (cachePath && cacheFile.exists()) {
|
||||
return _requireFile(cacheFile, optional)
|
||||
}
|
||||
// check core module
|
||||
name = checkCoreModule(name, path, optional)
|
||||
var file = resolve(name, path, optional)
|
||||
// search module
|
||||
if (file === undefined) {
|
||||
try {
|
||||
// excloud local dir, prevent too many recursive call and cache not found module
|
||||
if (name.startsWith('.') || name.startsWith('/') || optional.recursive || notFoundModules[name]) {
|
||||
console.log(name, path, optional, notFoundModules[name])
|
||||
throw new Error("Can't found module " + name + '(' + JSON.stringify(optional) + ') at local ' + path + ' or network!')
|
||||
if (optional.local || optional.recursive || notFoundModules[name]) {
|
||||
delete optional.parent
|
||||
throw __error("can't found module " + name + '(' + JSON.stringify(optional) + ') at local ' + path + ' or network!')
|
||||
}
|
||||
optional.recursive = true;
|
||||
return _require(download(name), path, optional);
|
||||
} catch (ex) {
|
||||
notFoundModules[name] = true;
|
||||
throw new FileNotFoundException("Can't found module " + name + ' in directory ' + path + ' ERROR: ' + ex)
|
||||
optional.recursive = true
|
||||
return _require(download(name, optional, 1), path, optional)
|
||||
}
|
||||
setCacheModule(file, optional)
|
||||
return _requireFile(file, optional)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模块缓存
|
||||
* @param {any} file
|
||||
* @param {any} optional
|
||||
*/
|
||||
function setCacheModule(file, optional) {
|
||||
if (optional.local) {
|
||||
var parent = cacheModuleIds[optional.parent.id]
|
||||
if (!parent) {
|
||||
cacheModuleIds[optional.parent.id] = {}
|
||||
}
|
||||
return cacheModuleIds[optional.parent.id][optional.path] = _canonical(file)
|
||||
}
|
||||
return cacheModuleIds[optional.path] = _canonical(file)
|
||||
}
|
||||
|
||||
function _requireFile(file, optional) {
|
||||
// 重定向文件名称和类型
|
||||
return getCacheModule(_canonical(file), file.name.split('.')[0], file, optional);
|
||||
return getCacheModule(_canonical(file), file, optional)
|
||||
}
|
||||
|
||||
/**
|
||||
* 闭包方法
|
||||
* @param {string} parent 父目录
|
||||
* @param {string} parentId
|
||||
* @param {any} parent 父模块
|
||||
* @returns {Function}
|
||||
*/
|
||||
function exports(parent, parentId) {
|
||||
var __DynamicRequire__ =
|
||||
function exports(parent) {
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {any} optional
|
||||
*/
|
||||
function __DynamicRequire__(path, optional) {
|
||||
return _require(path, parent, __assign({ parentId: parentId }, optional)).exports;
|
||||
var require = function __DynamicRequire__(path, optional) {
|
||||
if (!path) {
|
||||
throw __error("require path can't be undefined or empty!")
|
||||
}
|
||||
return __DynamicRequire__
|
||||
var optional = __assign({
|
||||
cache: true,
|
||||
parent: parent,
|
||||
path: path,
|
||||
local: path.startsWith('.') || path.startsWith('/')
|
||||
}, optional)
|
||||
return _require(path, parent.path, optional).exports
|
||||
}
|
||||
require.resolve = function __DynamicResolve__(path, optional) {
|
||||
return _canonical(new File(resolve(path, root, __assign({
|
||||
parent: parent,
|
||||
cache: true,
|
||||
local: path.startsWith('.') || path.startsWith('/')
|
||||
}, optional))))
|
||||
}
|
||||
return require
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} parent
|
||||
* @param {string} parentId
|
||||
*/
|
||||
function getRequire(parent, parentId) {
|
||||
/**
|
||||
* @type {any} require
|
||||
*/
|
||||
var require = exports(parent, parentId)
|
||||
require.resolve =
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function __DynamicResolve__(name) {
|
||||
return _canonical(new File(resolve(name, parent)))
|
||||
}
|
||||
require.clear =
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function __DynamicClear__(name) {
|
||||
var moduleId = require.resolve(name)
|
||||
console.trace('Clear module ' + name + '(' + moduleId + ') ...')
|
||||
return delete cacheModules[moduleId]
|
||||
for (var cacheModule in cacheModules) {
|
||||
if (cacheModule.indexOf(name) !== -1) {
|
||||
console.trace('clear module ' + cacheModule + ' ...')
|
||||
delete cacheModules[cacheModule]
|
||||
}
|
||||
return require;
|
||||
}
|
||||
}
|
||||
|
||||
function __DynamicDisable__() {
|
||||
base.save(cacheModuleIdsFile, JSON.stringify(upgradeMode ? {} : cacheModuleIds))
|
||||
for (var cacheModule in cacheModules) {
|
||||
delete cacheModules[cacheModule]
|
||||
}
|
||||
cacheModules = {}
|
||||
for (var cacheModuleId in cacheModuleIds) {
|
||||
delete cacheModuleIds[cacheModuleId]
|
||||
}
|
||||
cacheModuleIds = {}
|
||||
notFoundModules = {}
|
||||
}
|
||||
|
||||
function __setUpgradeMode__(status) {
|
||||
upgradeMode = status
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} parent
|
||||
*/
|
||||
function getRequire(parent) {
|
||||
/**
|
||||
* @type {any} require
|
||||
*/
|
||||
var require = exports(parent)
|
||||
require.main = mainRequire
|
||||
require.cache = cacheModules
|
||||
require.clear = __DynamicClear__
|
||||
require.disable = __DynamicDisable__
|
||||
require.setUpgradeMode = __setUpgradeMode__
|
||||
require.loader = {
|
||||
register: registerLoader,
|
||||
get: getLoader,
|
||||
unregister: unregisterLoader,
|
||||
}
|
||||
require.loaders = requireLoaders
|
||||
require.internal = {
|
||||
coreModules: CoreModules,
|
||||
cacheModules: cacheModules,
|
||||
cacheModuleIds: cacheModuleIds,
|
||||
notFoundModules: notFoundModules,
|
||||
versionLockModules: VersionLockModules
|
||||
}
|
||||
require.loadCoreScript = loadCoreScript
|
||||
return require
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} ext
|
||||
* @param {any} loader
|
||||
*/
|
||||
function registerLoader(ext, loader) {
|
||||
if (requireLoaders[ext]) {
|
||||
return console.error('require loader ' + ext + ' already register ignore. if you want override loader please unregister before register.')
|
||||
}
|
||||
requireExts.push(ext)
|
||||
requireLoaders[ext] = loader
|
||||
console.info('register require loader ' + ext + ' => ' + (loader.name || '<anonymous>') + '.')
|
||||
}
|
||||
/**
|
||||
* @param {*} ext
|
||||
*/
|
||||
function getLoader(ext) {
|
||||
return requireLoaders[ext]
|
||||
}
|
||||
/**
|
||||
* @param {*} ext
|
||||
*/
|
||||
function unregisterLoader(ext) {
|
||||
requireExts.splice(requireExts.indexOf(ext), 1);
|
||||
delete requireLoaders[ext]
|
||||
console.info('unregister require loader ' + ext + '.')
|
||||
}
|
||||
|
||||
function printRequireInfo() {
|
||||
console.info('Initialization require module.')
|
||||
console.info('ParentDir:', root)
|
||||
console.info('Require module env list:')
|
||||
console.info('- JAVA_VERSION:', System.getProperty("java.version"))
|
||||
console.info('- PLUGIN_VERSION:', base.version)
|
||||
console.info('- MS_NODE_PATH:', MS_NODE_PATH.startsWith(root) ? MS_NODE_PATH.split(root)[1] : MS_NODE_PATH)
|
||||
console.info('- MS_NODE_REGISTRY:', MS_NODE_REGISTRY)
|
||||
console.info('- MS_FALLBACK_NODE_REGISTRY:', MS_FALLBACK_NODE_REGISTRY)
|
||||
}
|
||||
|
||||
function initCacheModuleIds() {
|
||||
try {
|
||||
cacheModuleIds = JSON.parse(base.read(cacheModuleIdsFile))
|
||||
if (cacheModuleIds['@ccms-cache-module-root'] != MS_NODE_PATH) {
|
||||
throw __error('canonicalRoot Change ' + cacheModuleIds['@ccms-cache-module-root'] + ' to ' + MS_NODE_PATH + ' Clear Cache!')
|
||||
}
|
||||
console.log('Read cacheModuleIds from file', cacheModuleIdsFile.startsWith(root) ? cacheModuleIdsFile.split(root)[1] : cacheModuleIdsFile)
|
||||
} catch (error) {
|
||||
cacheModuleIds = {}
|
||||
cacheModuleIds['@ccms-cache-module-root'] = MS_NODE_PATH
|
||||
console.log('Initialization new cacheModuleIds: ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
function initVersionLock() {
|
||||
try {
|
||||
var version_lock_url = MS_SCRIPT_PACKAGE_CENTER + '?name=version_lock' + (global.debug ? '-debug' : '')
|
||||
VersionLockModules = JSON.parse(fetchContent(version_lock_url, 5000))
|
||||
try {
|
||||
VersionLockModules = __assign(VersionLockModules, JSON.parse(base.read(localVersionLockFile)))
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("无法获取到最新的版本锁定信息 使用默认配置.")
|
||||
console.warn("InitVersionLock Error:", error)
|
||||
console.debug(error)
|
||||
VersionLockModules = {
|
||||
"@babel/standalone": "7.12.18",
|
||||
"crypto-js": "3.3.0",
|
||||
"core-js": "3.33.1"
|
||||
}
|
||||
}
|
||||
console.info('Lock module version List:')
|
||||
for (var key in VersionLockModules) {
|
||||
console.info('- ' + key + ': ' + VersionLockModules[key])
|
||||
}
|
||||
}
|
||||
|
||||
function initRequireLoader(require) {
|
||||
registerLoader('js', compileJsFile)
|
||||
registerLoader('json', compileJson)
|
||||
try {
|
||||
loadCoreScript('require_loader')(require)
|
||||
} catch (error) {
|
||||
console.warn("无法获取到最新的加载器信息 使用默认配置.")
|
||||
console.warn("InitRequireLoader Error:", error)
|
||||
console.debug(error)
|
||||
registerLoader('ms', compileJsFile)
|
||||
}
|
||||
require.main = mainRequire = require
|
||||
return require
|
||||
}
|
||||
|
||||
function loadCoreScript(name) {
|
||||
return engineLoad({
|
||||
script: fetchContent(MS_SCRIPT_PACKAGE_CENTER + '?name=' + name, 5000),
|
||||
name: 'core/' + name + '.js'
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof parent === 'string') {
|
||||
parent = new File(parent);
|
||||
parent = new File(parent)
|
||||
}
|
||||
var mainRequire = undefined
|
||||
/**
|
||||
* @type {{[key:string]:any}} cacheModules
|
||||
* require 支持的后缀
|
||||
* @type {string[]} requireExts
|
||||
*/
|
||||
var cacheModules = {};
|
||||
var requireExts = []
|
||||
/**
|
||||
* @type {{[key:string]:string}} cacheModules
|
||||
* require加载器
|
||||
* @type {{[key:string]:(module:any, file:string, optional?:any)=>any}} requireLoader
|
||||
*/
|
||||
var cacheModuleIds = {};
|
||||
var requireLoaders = {}
|
||||
/**
|
||||
* @type {{[key:string]:boolean}} cacheModules
|
||||
* 已缓存的模块
|
||||
* @type {{[key:string]:any}} [cacheModules]
|
||||
*/
|
||||
var notFoundModules = {};
|
||||
console.info('Initialization require module. ParentDir:', _canonical(parent));
|
||||
return getRequire(parent, "null");
|
||||
});
|
||||
var cacheModules = {}
|
||||
var cacheModuleIdsFile = _canonical(new File(MS_NODE_PATH, 'cacheModuleIds.json'))
|
||||
var localVersionLockFile = _canonical(new File(MS_NODE_PATH, 'moduleVersionLock.json'))
|
||||
/**
|
||||
* 已缓存的模块ID
|
||||
* @type {{[key:string]:{[key:string]:string}|string}} [cacheModuleIds]
|
||||
*/
|
||||
var cacheModuleIds = {}
|
||||
/**
|
||||
* 未找到的模块
|
||||
* @type {{[key:string]:boolean}}
|
||||
*/
|
||||
var notFoundModules = {}
|
||||
var upgradeMode = false
|
||||
var executor = Executors.newSingleThreadExecutor(function (r) {
|
||||
return new Thread(r, "MiaoScript require thread")
|
||||
})
|
||||
|
||||
printRequireInfo()
|
||||
initCacheModuleIds()
|
||||
initVersionLock()
|
||||
|
||||
return initRequireLoader(getRequire({
|
||||
id: 'main',
|
||||
path: root
|
||||
}))
|
||||
})
|
||||
|
@ -1,69 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Hello Wrold 测试插件
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
|
||||
var event = require('api/event');
|
||||
var wrapper = require('api/wrapper');
|
||||
var command = require('api/command');
|
||||
var server = require('api/server');
|
||||
var fs = require('fs');
|
||||
|
||||
var description = {
|
||||
name: 'HelloWorld',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo',
|
||||
commands: {
|
||||
'hello': {
|
||||
description: 'HelloWorld主命令'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function load() {
|
||||
console.log('载入 Hello Wrold 测试插件!');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
command.on(this, 'hello', {
|
||||
cmd: function(sender, command, args) {
|
||||
engineLoad(fs.file(root, 'test.js'));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
console.log('启用 Hello World 测试插件!');
|
||||
switch (DetectServerType) {
|
||||
case ServerType.Bukkit:
|
||||
event.on(this, 'PlayerLoginEvent', function join(event) {
|
||||
send(event, wrapper.player(event.player));
|
||||
});
|
||||
break;
|
||||
case ServerType.Sponge:
|
||||
event.on(this, 'ClientConnectionEvent.Join', function join(event) {
|
||||
send(event, wrapper.player(event.targetEntity));
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function send(event, player) {
|
||||
// noinspection JSUnresolvedVariable
|
||||
console.debug('玩家', player.getName(), "触发事件", event.class.simpleName);
|
||||
setTimeout(function() {
|
||||
// noinspection JSUnresolvedVariable
|
||||
player.sendMessage("§a欢迎来到 §bMiaoScript §a的世界! 当前在线: " + server.players().length)
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
console.log('卸载 Hello World 测试插件!');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,88 +0,0 @@
|
||||
'use strict';
|
||||
/*global Java, base, module, exports, require*/
|
||||
var event = require('api/event');
|
||||
var task = require('api/task');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
|
||||
var Keys;
|
||||
|
||||
var description = {
|
||||
name: 'ItemTag',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo'
|
||||
};
|
||||
|
||||
var itemConfig;
|
||||
|
||||
function load() {
|
||||
var itemFile = self.file('item.yml');
|
||||
task.async(function() {
|
||||
if (!itemFile.exists()) {
|
||||
fs.save(itemFile, http.get('https://data.yumc.pw/config/Item_zh_CN.yml'))
|
||||
}
|
||||
itemConfig = self.getConfig('item.yml')
|
||||
})
|
||||
}
|
||||
|
||||
function enable() {
|
||||
switch (DetectServerType) {
|
||||
case ServerType.Bukkit:
|
||||
var i = require('api/item').create('STONE')
|
||||
if (!i.setCustomName) { return }
|
||||
event.on(self, 'ItemMergeEvent', function(event) {
|
||||
bukkit(event.target, event.entity.itemStack.amount + event.target.itemStack.amount);
|
||||
});
|
||||
event.on(self, 'ItemSpawnEvent', function(event) {
|
||||
if (event.entity.itemStack) {
|
||||
bukkit(event.entity, event.entity.itemStack.amount);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case ServerType.Sponge:
|
||||
Keys = Java.type('org.spongepowered.api.data.key.Keys');
|
||||
event.on(self, 'ItemMergeItemEvent', function(event) {
|
||||
// Sponge 暂未实现当前事件
|
||||
});
|
||||
event.on(self, 'SpawnEntityEvent', function(event) {
|
||||
event.entities.forEach(function(entity) {
|
||||
if (entity.type.name === "item") sponge(entity);
|
||||
})
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function bukkit(item, amount) {
|
||||
item.setCustomName('§b' + getItemName(item.itemStack.type) + getItemCount(amount));
|
||||
item.setCustomNameVisible(true);
|
||||
}
|
||||
|
||||
function sponge(entity) {
|
||||
var itemOptional = entity.get(Keys['REPRESENTED_ITEM']);
|
||||
if (itemOptional.isPresent()) {
|
||||
var item = itemOptional.get();
|
||||
var itemName = '§b' + getItemName(item.type.name.split(':')[1]) + getItemCount(item.count);
|
||||
entity.offer(Keys['DISPLAY_NAME'], org.spongepowered.api.text.Text.of(itemName));
|
||||
entity.offer(Keys['CUSTOM_NAME_VISIBLE'], true);
|
||||
}
|
||||
}
|
||||
|
||||
function getItemName(name) {
|
||||
return itemConfig[(name + '').toUpperCase()] || name;
|
||||
}
|
||||
|
||||
function getItemCount(amount) {
|
||||
return amount === 1 ? "" : "*" + amount;
|
||||
}
|
||||
|
||||
function disable() {
|
||||
console.log('卸载', description.name, '插件!');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,70 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* MiaoAuth简易登录系统
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
|
||||
var event = require('api/event');
|
||||
var wrapper = require('api/wrapper');
|
||||
var command = require('api/command');
|
||||
|
||||
var description = {
|
||||
name: 'MiaoAuth',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo',
|
||||
commands: {
|
||||
'l': {
|
||||
description: 'MiaoAuth登录命令'
|
||||
},
|
||||
'r': {
|
||||
description: 'MiaoAuth注册命令'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function load() {
|
||||
console.log('载入 MiaoAuth 插件!');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
command.on(this, 'l', {
|
||||
cmd: function(sender, command, args) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
command.on(this, 'r', {
|
||||
cmd: function(sender, command, args) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
console.log('启用 MiaoAuth 测试插件!');
|
||||
switch (DetectServerType) {
|
||||
case ServerType.Bukkit:
|
||||
event.on(this, 'playerloginevent', function join(event) {
|
||||
send(wrapper.player(event.player));
|
||||
});
|
||||
break;
|
||||
case ServerType.Sponge:
|
||||
event.on(this, 'clientconnectionevent.join', function join(event) {
|
||||
send(wrapper.player(event.targetEntity));
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function send(player) {
|
||||
setTimeout(function sendMessage() {
|
||||
player.sendMessage('§a输入 /l <密码> 以登录!');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
console.log('卸载 MiaoAuth 测试插件!');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,261 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* MiaoChat 喵式聊天插件
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
|
||||
var event = require('api/event');
|
||||
var command = require('api/command');
|
||||
|
||||
var tellraw = require('tellraw');
|
||||
var papi = require('papi');
|
||||
var utils = require('utils');
|
||||
|
||||
var Player;
|
||||
|
||||
var description = {
|
||||
name: 'MiaoChat',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo',
|
||||
commands: {
|
||||
'mchat': {
|
||||
description: 'MiaoChat登录命令'
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
'MiaoChat.default': {
|
||||
default: true,
|
||||
description: '默认权限 赋予玩家'
|
||||
},
|
||||
'MiaoChat.admin': {
|
||||
default: false,
|
||||
description: '管理权限'
|
||||
}
|
||||
},
|
||||
config: {
|
||||
Version: "1.8.5",
|
||||
BungeeCord: true,
|
||||
Server: "生存服",
|
||||
ChatFormats: {
|
||||
"default": {
|
||||
"index": 50,
|
||||
"permission": "MiaoChat.default",
|
||||
"range": 0,
|
||||
"format": "[world][player]&7: ",
|
||||
"item": true,
|
||||
"itemformat": "&6[&b%s&6]&r"
|
||||
},
|
||||
"admin": {
|
||||
"index": 49,
|
||||
"permission": "MiaoChat.admin",
|
||||
"format": "[admin][world][player][help]&7: ",
|
||||
"range": 0,
|
||||
"item": true,
|
||||
"itemformat": "&6[&b%s&6]&r"
|
||||
}
|
||||
},
|
||||
StyleFormats: {
|
||||
"world": {
|
||||
"text": "&6[&a%player_world%&6]",
|
||||
"hover": [
|
||||
"&6当前所在位置:",
|
||||
"&6世界: &d%player_world%",
|
||||
"&6坐标: &aX:%player_x% Y: %player_y% Z: %player_z%",
|
||||
"",
|
||||
"&c点击即可TP我!"
|
||||
],
|
||||
"click": {
|
||||
"type": "COMMAND",
|
||||
"command": "/tpa %player_name%"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"text": "&b%player_name%",
|
||||
"hover": [
|
||||
"&6玩家名称: &b%player_name%",
|
||||
"&6玩家等级: &a%player_level%",
|
||||
"&6玩家血量: &c%player_health%",
|
||||
"&6玩家饥饿: &d%player_food_level%",
|
||||
"&6游戏模式: &4%player_gamemode%",
|
||||
"",
|
||||
"&c点击与我聊天"
|
||||
],
|
||||
"click": {
|
||||
"type": "SUGGEST",
|
||||
"command": "/tell %player_name%"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"text": "&6[&c管理员&6]"
|
||||
},
|
||||
"help": {
|
||||
"text": "&4[求助]",
|
||||
"hover": [
|
||||
"点击求助OP"
|
||||
],
|
||||
"click": {
|
||||
"type": "COMMAND",
|
||||
"command": "管理员@%player_name% 我需要你的帮助!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var chat_formats;
|
||||
var style_formats;
|
||||
|
||||
function load() {
|
||||
chat_formats = Object.values(self.config.ChatFormats);
|
||||
chat_formats.sort(utils.compare('index'));
|
||||
initFormat(chat_formats);
|
||||
style_formats = self.config.StyleFormats;
|
||||
}
|
||||
|
||||
// 用于匹配 '[xx]' 聊天格式
|
||||
var FORMAT_PATTERN = /[\[]([^\[\]]+)[\]]/ig;
|
||||
|
||||
function initFormat(chat_formats) {
|
||||
chat_formats.forEach(function(chat_format) {
|
||||
var chat_format_str = chat_format.format;
|
||||
var temp = [];
|
||||
var r;
|
||||
while (r = FORMAT_PATTERN.exec(chat_format_str)) {
|
||||
temp.push(r[1]);
|
||||
}
|
||||
var format_list = [];
|
||||
temp.forEach(function splitStyle(t) {
|
||||
var arr = chat_format_str.split('[' + t + ']', 2);
|
||||
if (arr[0]) {
|
||||
format_list.push(arr[0]);
|
||||
}
|
||||
format_list.push(t);
|
||||
chat_format_str = arr[1];
|
||||
});
|
||||
if (chat_format_str) {
|
||||
format_list.push(chat_format_str);
|
||||
}
|
||||
chat_format.format_list = format_list;
|
||||
})
|
||||
}
|
||||
|
||||
function enable() {
|
||||
registerCommand();
|
||||
registerEvent();
|
||||
}
|
||||
|
||||
function registerCommand() {
|
||||
command.on(self, 'mchat', {
|
||||
cmd: mainCommand
|
||||
});
|
||||
}
|
||||
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
function mainCommand(sender, command, args) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function registerEvent() {
|
||||
switch (DetectServerType) {
|
||||
case ServerType.Bukkit:
|
||||
event.on(self, 'AsyncPlayerChatEvent', handlerBukkitChat);
|
||||
break;
|
||||
case ServerType.Sponge:
|
||||
Player = org.spongepowered.api.entity.living.player.Player;
|
||||
event.on(self, 'MessageChannelEvent.Chat', handlerSpongeChat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handlerBukkitChat(event) {
|
||||
sendChat(event.player, event.message, function() {
|
||||
event.setCancelled(true);
|
||||
});
|
||||
}
|
||||
|
||||
function handlerSpongeChat(event) {
|
||||
var player = event.getCause().first(Player.class).orElse(null);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
var plain = event.getRawMessage().toPlain();
|
||||
if (plain.startsWith(tellraw.duplicateChar)) {
|
||||
return;
|
||||
}
|
||||
sendChat(player, plain, function() {
|
||||
event.setMessageCancelled(true)
|
||||
});
|
||||
}
|
||||
|
||||
function sendChat(player, plain, callback) {
|
||||
var chat_format = getChatFormat(player);
|
||||
if (!chat_format) {
|
||||
console.debug('未获得用户', player.name, '的 ChatRule 跳过执行...');
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
var tr = tellraw.create();
|
||||
chat_format.format_list.forEach(function setStyle(format) {
|
||||
var style = style_formats[format];
|
||||
if (style) {
|
||||
tr.then(replace(player, style.text));
|
||||
if (style.hover) {
|
||||
tr.tip(replace(player, style.hover));
|
||||
}
|
||||
if (style.click && style.click.type && style.click.command) {
|
||||
var command = replace(player, style.click.command);
|
||||
switch (style.click.type) {
|
||||
case "COMMAND":
|
||||
tr.command(command);
|
||||
break;
|
||||
case "OPENURL":
|
||||
tr.link(command);
|
||||
break;
|
||||
case "SUGGEST":
|
||||
tr.suggest(command);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tr.then(replace(player, format));
|
||||
}
|
||||
});
|
||||
tr.then(replace(player, plain)).sendAll();
|
||||
}
|
||||
|
||||
function getChatFormat(player) {
|
||||
for (var i in chat_formats) {
|
||||
var format = chat_formats[i];
|
||||
if (player.hasPermission(format.permission)) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function replace(player, target) {
|
||||
if (toString.call(target) === "[object Array]") {
|
||||
for (var i in target) {
|
||||
target[i] = replaceStr(player, target[i]);
|
||||
}
|
||||
} else {
|
||||
target = replaceStr(player, target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function replaceStr(player, target) {
|
||||
return papi.$(player, target);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
console.log('卸载', description.name, '插件!');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,231 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Hello Wrold 测试插件
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
|
||||
var event = require('api/event');
|
||||
var command = require('api/command');
|
||||
var bukkit = require('api/server');
|
||||
var item = require('api/item');
|
||||
|
||||
var Arrays = Java.type('java.util.Arrays');
|
||||
var ItemStackArray = Java.type('org.bukkit.inventory.ItemStack[]');
|
||||
|
||||
var PANE = 'STAINED_GLASS_PANE'
|
||||
|
||||
var description = {
|
||||
name: 'Lottery',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo',
|
||||
commands: {
|
||||
'lottery': {
|
||||
description: 'Lottery主命令'
|
||||
}
|
||||
},
|
||||
config: {
|
||||
title: '§m§s§a幸运抽奖',
|
||||
control: {
|
||||
panel: PANE + ':13',
|
||||
ok: PANE + ':14',
|
||||
no: PANE + ':15',
|
||||
},
|
||||
list: [
|
||||
{
|
||||
box: {
|
||||
id: PANE,
|
||||
damage: 1,
|
||||
name: '§a箱子',
|
||||
lore: [
|
||||
'这是箱子的Lore'
|
||||
]
|
||||
},
|
||||
key: {
|
||||
id: PANE,
|
||||
damage: 2,
|
||||
name: '§b钥匙',
|
||||
lore: [
|
||||
'这是钥匙的Lore'
|
||||
]
|
||||
},
|
||||
result: [
|
||||
{
|
||||
percent: 10,
|
||||
command: 'money give %player% 100',
|
||||
item: {
|
||||
id: PANE,
|
||||
damage: 3,
|
||||
name: '§c奖品1',
|
||||
lore: [
|
||||
'这是奖品1的Lore'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
percent: 20,
|
||||
command: 'money give %player% 200',
|
||||
item: {
|
||||
id: PANE,
|
||||
damage: 4,
|
||||
name: '§c奖品2',
|
||||
lore: [
|
||||
'这是奖品2的Lore'
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var panel;
|
||||
var config;
|
||||
var items;
|
||||
|
||||
function load() {
|
||||
config = this.config;
|
||||
panel = newItemFromString(config.control.panel || PANE + ':13')
|
||||
items = new ItemStackArray(54);
|
||||
item.setName(panel, '');
|
||||
var ok = newItemFromString(config.control.ok || PANE + ':14')
|
||||
item.setName(ok, '§a确定抽奖');
|
||||
var no = newItemFromString(config.control.no || PANE + ':15')
|
||||
item.setName(no, '§c取消抽奖');
|
||||
Arrays.fill(items, 0, 10, panel);
|
||||
Arrays.fill(items, 11, 16, panel);
|
||||
Arrays.fill(items, 17, 29, panel);
|
||||
items[29] = no;
|
||||
Arrays.fill(items, 30, 33, panel);
|
||||
items[33] = ok;
|
||||
Arrays.fill(items, 34, 40, panel);
|
||||
Arrays.fill(items, 41, 54, panel);
|
||||
}
|
||||
|
||||
function isTargetItem(item, config) {
|
||||
return item.typeId === config.id &&
|
||||
item.itemMeta &&
|
||||
item.itemMeta.displayName === config.name &&
|
||||
item.itemMeta.lore && Java.from(item.itemMeta.lore).toJson() === config.lore.toJson()
|
||||
}
|
||||
|
||||
function newItem(name, sub) {
|
||||
return item.create(name, 1, sub || 0);
|
||||
}
|
||||
|
||||
function newItemFromString(str) {
|
||||
var arr = str.split(':');
|
||||
if (arr.length === 2) {
|
||||
return newItem(arr[0], arr[1]);
|
||||
} else {
|
||||
return newItem(arr[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function newItemFromConfig(config) {
|
||||
var i = newItem(config.id, config.damage);
|
||||
if (config.name) item.setName(i, config.name);
|
||||
if (config.lore) item.setLore(i, config.lore);
|
||||
return i;
|
||||
}
|
||||
|
||||
function enable() {
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
command.on(this, 'l', {
|
||||
cmd: function(sender, command, args) {
|
||||
sender.inventory.addItem(newItemFromConfig(config.list[0].box))
|
||||
sender.inventory.addItem(newItemFromConfig(config.list[0].key))
|
||||
if (!sender.openInventory) {
|
||||
console.sender(sender, "§4当前用户无法使用该命令!");
|
||||
}
|
||||
var inv = MServer.createInventory(null, 54, config.title);
|
||||
inv.setContents(items);
|
||||
sender.openInventory(inv);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
event.on(this, 'InventoryClick', function click(event) {
|
||||
var inv = event.inventory;
|
||||
if (inv && inv.title !== config.title) return;
|
||||
var player = event.whoClicked;
|
||||
var slot = event.rawSlot;
|
||||
if (slot > 53 || slot < 0) {
|
||||
return;
|
||||
}
|
||||
event.cancelled = true;
|
||||
switch (slot) {
|
||||
case 10:
|
||||
case 16:
|
||||
case 40:
|
||||
event.cancelled = false;
|
||||
break;
|
||||
case 29:
|
||||
// TODO 关闭界面
|
||||
player.closeInventory();
|
||||
break;
|
||||
case 33:
|
||||
var temp = inv.getItem(40);
|
||||
if (temp && temp.typeId !== 0) {
|
||||
console.sender(player, '§c请先取走奖品!');
|
||||
return;
|
||||
}
|
||||
var litem;
|
||||
var box = inv.getItem(10);
|
||||
if (!box) {
|
||||
console.sender(player, '§c请先放入抽奖物品和钥匙!');
|
||||
return;
|
||||
}
|
||||
var key = inv.getItem(16);
|
||||
if (box && box.typeId !== 0 && key && key.typeId !== 0) {
|
||||
for (var i = 0; i < config.list.length; i++) {
|
||||
var r = config.list[i];
|
||||
if (isTargetItem(box, r.box)) {
|
||||
litem = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO 抽奖
|
||||
if (!litem) {
|
||||
console.sender(player, '§c请先放入抽奖物品和钥匙!');
|
||||
return;
|
||||
}
|
||||
if (!isTargetItem(key, litem.key)) {
|
||||
console.sender(player, '§c抽奖物品和钥匙不匹配!');
|
||||
return;
|
||||
}
|
||||
var resultList = [];
|
||||
litem.result.forEach(function(t) {
|
||||
for (var i = 0; i < t.percent; i++) {
|
||||
resultList.push(t);
|
||||
}
|
||||
});
|
||||
var ri = random(resultList.length);
|
||||
var result = resultList[ri];
|
||||
box.amount = box.amount - 1;
|
||||
key.amount = key.amount - 1;
|
||||
inv.setItem(10, box);
|
||||
inv.setItem(16, key);
|
||||
inv.setItem(40, newItemFromConfig(result.item));
|
||||
bukkit.console(result.command.replace('%player%', player.name));
|
||||
break;
|
||||
default:
|
||||
event.cancelled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function random(max, min) {
|
||||
min = min === undefined ? 0 : min;
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
};
|
||||
|
||||
function disable() {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,188 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* MiaoTag
|
||||
* 可兼容任何记分板
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
var event = require('api/event');
|
||||
var bukkit = require('api/server');
|
||||
var command = require('api/command');
|
||||
|
||||
var fakeTag;
|
||||
|
||||
var description = {
|
||||
name: 'MiaoTag',
|
||||
version: '1.1',
|
||||
author: 'MiaoWoo',
|
||||
config: {
|
||||
format: '§4§l❤'
|
||||
},
|
||||
commands: {
|
||||
'mtag': {
|
||||
description: 'MiaoTag主命令',
|
||||
usage: '',
|
||||
permission: 'MiaoTag.admin'
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
'MiaoTag.default': {
|
||||
default: true,
|
||||
description: '默认权限 赋予玩家'
|
||||
},
|
||||
'MiaoTag.admin': {
|
||||
default: false,
|
||||
description: '管理权限'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var config;
|
||||
|
||||
function load() {
|
||||
config = self.getConfig();
|
||||
}
|
||||
|
||||
function enable() {
|
||||
registryCommand()
|
||||
fakeTag = new FakeTag(config.format);
|
||||
registryEvent()
|
||||
}
|
||||
|
||||
function registryCommand() {
|
||||
command.on(self, 'mtag', {
|
||||
cmd: function cmd(sender, command, args) {
|
||||
var subCommand = args[0];
|
||||
switch (subCommand) {
|
||||
case 'reload':
|
||||
self.reloadConfig();
|
||||
fakeTag = new FakeTag(config.format);
|
||||
console.sender(sender, "§a配置文件重载完成!");
|
||||
break;
|
||||
}
|
||||
},
|
||||
tab: function tab(sender, command, args) {
|
||||
return ['reload'];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registryEvent() {
|
||||
bukkit.players(function(p) { fakeTag.set(p) });
|
||||
event.on(self, 'PlayerJoin', function(event) { fakeTag.set(event.player) });
|
||||
event.on(self, 'EntityRegainHealth', entityUpdate, false);
|
||||
event.on(self, 'EntityDamage', entityUpdate, false);
|
||||
event.on(self, 'EntityRegainHealth', entityUpdate, false);
|
||||
event.on(self, 'PlayerRespawn', entityUpdate, false);
|
||||
}
|
||||
|
||||
function entityUpdate(event) {
|
||||
var player = event.entity || event.player;
|
||||
if (player instanceof org.bukkit.entity.Player) {
|
||||
setTimeout(function() {
|
||||
fakeTag.update(player);
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
function disable() {
|
||||
if (fakeTag) { fakeTag.disable() };
|
||||
}
|
||||
|
||||
function FakeTag(name) {
|
||||
var ver1_13 = false;
|
||||
// NMS CLASS
|
||||
try {
|
||||
var ScoreboardBaseCriteria = bukkit.nmsCls('ScoreboardBaseCriteria');
|
||||
} catch (ex) {
|
||||
try {
|
||||
var IScoreboardCriteria = bukkit.nmsCls('IScoreboardCriteria');
|
||||
var ScoreboardServer = bukkit.nmsCls("ScoreboardServer");
|
||||
var ChatComponentText = bukkit.nmsCls('ChatComponentText');
|
||||
ver1_13 = true;
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
var PacketPlayOutScoreboardScore = bukkit.nmsCls('PacketPlayOutScoreboardScore');
|
||||
var PacketPlayOutScoreboardObjective = bukkit.nmsCls('PacketPlayOutScoreboardObjective');
|
||||
var PacketPlayOutScoreboardDisplayObjective = bukkit.nmsCls('PacketPlayOutScoreboardDisplayObjective');
|
||||
|
||||
var scoreboardManager = bukkit.$.scoreboardManager;
|
||||
var mainScoreboard = scoreboardManager.mainScoreboard.handle;
|
||||
|
||||
// 注销对象
|
||||
var objective = mainScoreboard.getObjective(name);
|
||||
if (objective) {
|
||||
mainScoreboard.unregisterObjective(objective);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!ver1_13) {
|
||||
// 注册tag对象
|
||||
objective = mainScoreboard.registerObjective(name, new ScoreboardBaseCriteria(name));
|
||||
} else {
|
||||
// 注册tag对象
|
||||
objective = mainScoreboard.registerObjective(name,
|
||||
IScoreboardCriteria.HEALTH,
|
||||
new ChatComponentText(name),
|
||||
IScoreboardCriteria.EnumScoreboardHealthDisplay.HEARTS);
|
||||
}
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
// ignore 忽略创建错误 eg: java.lang.IllegalArgumentException: An objective with the name 'xxxxx' already exists!
|
||||
}
|
||||
|
||||
if (!objective) {
|
||||
throw Error("Error Can't Found MainScoreboard Objective " + name)
|
||||
}
|
||||
|
||||
// 缓存虚拟的tag包
|
||||
var cache = {
|
||||
objective: new PacketPlayOutScoreboardObjective(objective, 0),
|
||||
display: new PacketPlayOutScoreboardDisplayObjective(2, objective)
|
||||
};
|
||||
|
||||
function sendPacket(player, p) {
|
||||
player.handle.playerConnection.sendPacket(p);
|
||||
}
|
||||
|
||||
this.set = function(player) {
|
||||
sendPacket(player, cache.objective);
|
||||
sendPacket(player, cache.display);
|
||||
this.update(player);
|
||||
};
|
||||
|
||||
function createScore(player) {
|
||||
if (!ver1_13) {
|
||||
var score = mainScoreboard.getPlayerScoreForObjective(player.name, objective);
|
||||
score.setScore(player.health);
|
||||
return new PacketPlayOutScoreboardScore(score);
|
||||
} else {
|
||||
return new PacketPlayOutScoreboardScore(ScoreboardServer.Action.CHANGE, name, player.name, player.health)
|
||||
}
|
||||
}
|
||||
|
||||
this.update = function update(player) {
|
||||
var scorePack = createScore(player);
|
||||
//把其他玩家缓存的包发给这个玩家
|
||||
bukkit.players(function(t) {
|
||||
sendPacket(t, scorePack);
|
||||
if (t.name !== player.name) {
|
||||
sendPacket(player, createScore(t));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.disable = function() {
|
||||
// 注销tag对象
|
||||
mainScoreboard.unregisterObjective(objective);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* WorldEdit 插件
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
var item = require('/api/item');
|
||||
var command = require('api/command');
|
||||
var Material = Java.type("org.bukkit.Material");
|
||||
|
||||
var description = {
|
||||
name: 'WorldEdit',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo',
|
||||
commands: {
|
||||
'/up': {
|
||||
description: 'Up Player And Set Block'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function load() {
|
||||
}
|
||||
|
||||
function enable() {
|
||||
command.on(this, '/up', {
|
||||
cmd: function(sender, command, args) {
|
||||
if (!sender.openInventory) {
|
||||
return;
|
||||
}
|
||||
var player = sender;
|
||||
var location = player.location;
|
||||
var type = item.type(args[0], 'STONE');
|
||||
player.velocity = player.velocity.setY(0.5);
|
||||
setTimeout(function() {
|
||||
location.block.type = type
|
||||
}, 6);
|
||||
return true;
|
||||
},
|
||||
tab: function(sender, command, args) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
enable: enable
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
## MiaoChat for MiaoScript
|
||||
|
||||

|
||||
|
||||
> 注意: MiaoScript 在Windows环境下 暂不支持 reload 所以 Windows 环境请重启服务器
|
||||
> 注意: MiaoScript 将不会计划兼容 非 `Sponge` 的MOD端
|
||||
|
||||
- 基于 `MiaoScript` 开发 同时兼容 `Sponge` 和 `Bukkit`
|
||||
- 支持PAPI
|
||||
- `Bukkit` 为 `me.clip.placeholderapi.PlaceholderAPI`
|
||||
- `Sponge` 为 `me.rojo8399.placeholderapi.PlaceholderService`
|
||||
- 支持悬浮提示
|
||||
- 支持点击执行命令
|
||||
- 支持点击命令补全
|
||||
|
||||
### Feature(开发规划)
|
||||
- 悬浮物品提示
|
||||
- 兼容 `BungeeCord` 支持跨服聊天
|
||||
- 兼容其他聊天插件 保护插件
|
||||
|
||||
### 安装教程
|
||||
- 下载 MiaoChat 本体并安装
|
||||
- [下载地址-论坛](http://www.mcbbs.net/thread-774401-1-1.html)
|
||||
- [下载地址-备用](https://git.yumc.pw/502647092/MiaoScript/releases)
|
||||
- 安装到服务端对应的目录
|
||||
- Bukkit => plugins
|
||||
- Sponge => mods
|
||||
- 重启服务器
|
||||
- 下载 安装 `MiaoScriptPackageManager`
|
||||
- [安装教程](http://www.mcbbs.net/thread-774797-1-1.html)
|
||||
- 使用 `MiaoScriptPackageManager` 安装插件
|
||||
- 执行 `mpm install MiaoChat` 即可安装成功
|
@ -1,220 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* MiaoBoard 喵式聊天插件
|
||||
*/
|
||||
/*global Java, base, module, exports, require*/
|
||||
var task = require('api/task');
|
||||
var event = require('api/event');
|
||||
var server = require('api/server');
|
||||
var command = require('api/command');
|
||||
|
||||
var papi = require('papi');
|
||||
|
||||
var Player;
|
||||
|
||||
var boards = [];
|
||||
|
||||
var description = {
|
||||
name: 'MiaoBoard',
|
||||
version: '1.0',
|
||||
author: 'MiaoWoo',
|
||||
commands: {
|
||||
'mboard': {
|
||||
description: '喵式记分板主命令'
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
'mb.default': {
|
||||
default: true,
|
||||
description: '默认权限 赋予玩家'
|
||||
},
|
||||
'mb.admin': {
|
||||
default: false,
|
||||
description: '管理权限'
|
||||
}
|
||||
},
|
||||
config: {
|
||||
"Version": 2,
|
||||
"UpdateTime": 10,
|
||||
"DisableWorld": [
|
||||
"WorldName"
|
||||
],
|
||||
"Boards": {
|
||||
"default": {
|
||||
"index": 50,
|
||||
"time": {
|
||||
"start": "2016-01-01 00:00:00",
|
||||
"end": "2020-01-01 00:00:00"
|
||||
},
|
||||
"title": "玩家信息",
|
||||
"permission": "mb.default",
|
||||
"lines": [
|
||||
"&6名 称: &a%player_displayname%",
|
||||
"&6世 界: &b%player_world%",
|
||||
"&6位 置: &3%player_x%,%player_y%,%player_z%",
|
||||
"&6等 级: &e%player_level%",
|
||||
"&6血 量: &c%player_health%",
|
||||
"&6模 式: &4%player_gamemode%"
|
||||
]
|
||||
},
|
||||
"admin": {
|
||||
"index": 49,
|
||||
"title": "服务器信息",
|
||||
"permission": "mb.reload",
|
||||
"lines": [
|
||||
"&6名 称: &aMiaoBoard",
|
||||
"&6版 本: &b1.0.0",
|
||||
"&6作 者: &cMiaoWoo",
|
||||
"&6人 数: &c%server_online%/%server_max%",
|
||||
"&6内 存: &a%server_ram_used%/%server_ram_total%/%server_ram_max%"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var update_task;
|
||||
var board_formats;
|
||||
|
||||
function load() {
|
||||
board_formats = self.config.Boards;
|
||||
}
|
||||
|
||||
function enable() {
|
||||
registerCommand();
|
||||
registerEvent();
|
||||
registerTask();
|
||||
updatePlayers()
|
||||
}
|
||||
|
||||
function registerCommand() {
|
||||
command.on(self, 'mboard', {
|
||||
cmd: mainCommand
|
||||
});
|
||||
}
|
||||
|
||||
function mainCommand(sender, command, args) {
|
||||
if (!args[0]) {
|
||||
return;
|
||||
}
|
||||
switch (args[0]) {
|
||||
case "reload":
|
||||
self.reloadConfig();
|
||||
load();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function registerEvent() {
|
||||
switch (DetectServerType) {
|
||||
case ServerType.Bukkit:
|
||||
//event.on(self, 'PlayerLoginEvent', handlerPlayerJoin);
|
||||
break;
|
||||
case ServerType.Sponge:
|
||||
Player = org.spongepowered.api.entity.living.player.Player;
|
||||
event.on(self, 'ClientConnectionEvent.Join', handlerPlayerJoin);
|
||||
event.on(self, 'ClientConnectionEvent.Disconnect', handlerPlayerQuit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePlayers() {
|
||||
switch (DetectServerType) {
|
||||
case ServerType.Bukkit:
|
||||
//event.on(self, 'PlayerLoginEvent', handlerPlayerJoin);
|
||||
break;
|
||||
case ServerType.Sponge:
|
||||
server.players(function(player) {
|
||||
boards[player.name] = new MiaoBoard(player);
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handlerPlayerJoin(event) {
|
||||
var player = event.player || event.targetEntity;
|
||||
boards[player.name] = new MiaoBoard(player);
|
||||
}
|
||||
|
||||
function handlerPlayerQuit(event) {
|
||||
var player = event.player || event.targetEntity;
|
||||
delete boards[player.name];
|
||||
}
|
||||
|
||||
function registerTask() {
|
||||
update_task = task.timerAsync(updateBoard, self.config.UpdateTime);
|
||||
}
|
||||
|
||||
function updateBoard() {
|
||||
for (var i in boards) {
|
||||
var player = server.player(i);
|
||||
if (player.isOnline()) {
|
||||
var format = getBoardFormat(player);
|
||||
if (format) {
|
||||
boards[i].update(papi.$(player, format.title), papi.$(player, format.lines));
|
||||
} else {
|
||||
boards[i].clear();
|
||||
}
|
||||
} else {
|
||||
delete boards[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBoardFormat(player) {
|
||||
for (var i in board_formats) {
|
||||
var format = board_formats[i];
|
||||
if (player.hasPermission(format.permission)) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function disable() {
|
||||
if (update_task) { update_task.cancel(); }
|
||||
}
|
||||
|
||||
function MiaoBoard(player) {
|
||||
var Scoreboard = Java.type('org.spongepowered.api.scoreboard.Scoreboard');
|
||||
var Objective = Java.type('org.spongepowered.api.scoreboard.objective.Objective');
|
||||
var Criteria = Java.type('org.spongepowered.api.scoreboard.critieria.Criteria');
|
||||
var Text = Java.type('org.spongepowered.api.text.Text');
|
||||
var DisplaySlots = Java.type('org.spongepowered.api.scoreboard.displayslot.DisplaySlots');
|
||||
|
||||
var uuid = player.uniqueId;
|
||||
var scoreboard = Scoreboard.builder().build();
|
||||
var sidebar = Objective.builder().criterion(Criteria.DUMMY).displayName(Text.EMPTY).name("Sidebar").build();
|
||||
|
||||
var origin = [];
|
||||
|
||||
scoreboard.addObjective(sidebar);
|
||||
player.setScoreboard(scoreboard);
|
||||
|
||||
this.update = function(title, lines) {
|
||||
this.updateBuffer(title, lines);
|
||||
}
|
||||
|
||||
this.updateBuffer = function(title, lines) {
|
||||
sidebar.scores.values().forEach(function removeScore(score) {
|
||||
sidebar.removeScore(score);
|
||||
})
|
||||
var i = 0;
|
||||
sidebar.setDisplayName(Text.of(title));
|
||||
lines.forEach(function addScore(line) {
|
||||
sidebar.getOrCreateScore(Text.of(line)).setScore(lines.length - i++);
|
||||
})
|
||||
scoreboard.updateDisplaySlot(sidebar, DisplaySlots.SIDEBAR);
|
||||
}
|
||||
|
||||
this.clear = function() {
|
||||
player.setScoreboard(Scoreboard.builder().build());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
description: description,
|
||||
load: load,
|
||||
enable: enable,
|
||||
disable: disable
|
||||
};
|
@ -1,10 +1,26 @@
|
||||
name: ${project.artifactId}
|
||||
description: ${project.description}
|
||||
main: ${project.groupId}.${project.artifactId}.${project.artifactId}
|
||||
main: ${project.groupId}.${project.artifactId}.${project.artifactId}Bukkit
|
||||
version: ${project.version}
|
||||
api-version: 1.13
|
||||
author: MiaoWoo
|
||||
website: ${ciManagement.url}
|
||||
load: STARTUP
|
||||
softdepend:
|
||||
- OriginAttribute
|
||||
- AttributeSystem
|
||||
- ItemLoreOrigin
|
||||
- PlaceholderAPI
|
||||
- AttributePlus
|
||||
- PlayerPoints
|
||||
- SX-Attribute
|
||||
- CrazyCrates
|
||||
- ProtocolLib
|
||||
- DragonCore
|
||||
- MythicMobs
|
||||
- WorldGuard
|
||||
- Adyeshach
|
||||
- WorldEdit
|
||||
- SkillAPI
|
||||
- TradeMe
|
||||
- Chemdah
|
||||
- Vault
|
||||
|
Reference in New Issue
Block a user