8 Commits

11 changed files with 256 additions and 67 deletions

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# MiaoScript
> 排版什么的 不存在的 这辈子都不会有排版的 除非什么时候论坛支持 `MarkDown` 了
### 简介
> 这个坑是我自己刨的 但是发现坑太大 需要更多的人一起填
#### 起源
- 诞生于 `2016年08月25日` 这是 Git 上的第一个提交 具体啥时候我也忘了
- 起初 `MiaoScript` 只是用于服务器其他插件的变量执行 并且依赖于PAPI(不知道是啥的自己百度)
- 比如 [`MiaoMenu`](http://w.yumc.pw/zc/MiaoMenu.html) 的部分复杂脚本
- 比如 [`MiaoChat`](http://mcbbs.tvt.im/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`
### 填坑
- 实际上说了那么多 最终希望的就是 有大佬能一起来填坑 毕竟这个坑太大了

10
pom.xml
View File

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>pw.yumc</groupId>
<artifactId>MiaoScript</artifactId>
<version>1.2.0</version>
<version>1.2.1</version>
<developers>
<developer>
<id>502647092</id>
@ -84,12 +84,16 @@
<properties>
<env.GIT_COMMIT>DEV</env.GIT_COMMIT>
<update.changes>
§618-01-09 §afeat: 优化加载流程 完善事件注册
§618-01-02 §afeat: 新增http网络访问模块
§617-12-28 §afeat: 完善Sponge事件注册 新增player封装类
</update.changes>
<update.changelog>
§617-11-30 §afeat: 新增Sponge的兼容;
§617-11-03 §afeat: 新增抽奖插件;
§617-10-16 §cfix: 修复关闭MiaoScript时task异常;
§617-10-15 §6alpha: 第一个版本发布;
</update.changes>
<update.changelog></update.changelog>
</update.changelog>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>

View File

@ -43,7 +43,7 @@ public class ScriptEngine {
@SneakyThrows
public void disableEngine() {
engine.invokeFunction("disable");
engine.invokeFunction("engineDisable");
}
public MiaoScriptEngine getEngine() {

View File

@ -111,8 +111,8 @@ function EventHandlerDefault() {
* @param ignoreCancel
*/
this.listen = function listen(jsp, event, exec, priority, ignoreCancel) {
if (!jsp || !jsp.description || !jsp.description.name) throw new TypeError('插件名称为空 请检查传入参数!');
var name = jsp.description.name;
if (ext.isNull(name)) throw new TypeError('插件名称为空 请检查传入参数!');
var eventCls = this.name2Class(name, event);
if (!eventCls) { return; }
if (typeof priority === 'boolean') {
@ -128,7 +128,7 @@ function EventHandlerDefault() {
if (!listenerMap[name]) listenerMap[name] = [];
var offExec = function () {
this.unregister(eventCls, listener);
console.debug('插件 %s 注销事件 %s'.format(name, this.class2Name(eventCls)));
console.debug('插件 %s 注销事件 %s'.format(name, eventCls.name.substring(eventCls.name.lastIndexOf(".") + 1)));
}.bind(this);
var off = {
event: eventCls,
@ -153,4 +153,4 @@ module.exports = {
delete EventHandler.listenerMap[jsp.description.name];
}
}
};
};

View File

@ -2,7 +2,7 @@
/**
* MiaoScript脚本插件加载类
*/
/*global Java, base, module, exports, require, __FILE__*/
/*global Java, module, exports, require, __FILE__*/
// var zip = require("core/zip");
var fs = require('core/fs');
var yaml = require('modules/yaml');
@ -87,7 +87,7 @@ function loadPlugin(file) {
hook: function (origin) {
return beforeLoadHook(origin);
}
});
})
console.debug("插件编译结果: %s".format(JSON.stringify(plugin)));
var desc = plugin.description;
if (!desc || !desc.name) {
@ -127,16 +127,15 @@ function initPlugin(file, plugin) {
// 初始化 __FILE__
plugin.__FILE__ = file;
// 初始化 __DATA__
plugin.__DATA__ = fs.file(file.parentFile, plugin.description.name);
plugin.__DATA__ = plugin.dataFolder = fs.file(file.parentFile, plugin.description.name);
// 初始化 getDataFolder()
plugin.getDataFolder = function () {
plugin.getDataFolder = function getDataFolder() {
return plugin.__DATA__;
};
}
// 初始化 getFile()
plugin.getFile = function (name) {
plugin.getFile = plugin.file = function getFile(name) {
return fs.file(plugin.getDataFolder(), name);
};
// 初始化插件配置相关方法
initPluginConfig(plugin);
@ -164,9 +163,9 @@ function initPluginConfig(plugin) {
if (!file.isFile) {
file = plugin.getFile(file);
}
return yaml.safeLoad(base.read(file));
return yaml.safeLoad(fs.read(file), { json: true });
}
};
}
/**
* 重载配置文件
* @constructor
@ -174,7 +173,7 @@ function initPluginConfig(plugin) {
*/
plugin.reloadConfig = function () {
plugin.config = plugin.getConfig(plugin.configFile);
};
}
/**
* 保存配置文件
* @constructor
@ -184,13 +183,13 @@ function initPluginConfig(plugin) {
switch (arguments.length) {
case 0:
plugin.configFile.parentFile.mkdirs();
base.save(plugin.configFile, yaml.safeDump(plugin.config));
fs.save(plugin.configFile, yaml.safeDump(plugin.config));
break;
case 2:
base.save(arguments[0], yaml.safeDump(arguments[1]));
fs.save(fs.file(plugin.__DATA__, arguments[0]), yaml.safeDump(arguments[1]));
break;
}
};
}
if (plugin.configFile.isFile()) {
plugin.config = plugin.getConfig('config.yml');
} else if (plugin.description.config) {
@ -199,10 +198,6 @@ function initPluginConfig(plugin) {
}
}
function runAndCatch(jsp, name, ext) {
}
function checkAndGet(args) {
if (args.length === 0) {
return plugins;
@ -223,15 +218,13 @@ function checkAndRun(args, name, ext) {
for (var i in pls) {
var jsp = pls[i];
var exec = jsp[name];
if (exec) {
try {
// 绑定方法的this到插件自身
exec.bind(jsp)();
if (ext) ext.bind(jsp)();
} catch (ex) {
console.console('§6插件 §b%s §6执行 §d%s §6方法时发生错误 §4%s'.format(jsp.description.name, name, ex.message));
console.ex(ex);
}
try {
// 绑定方法的this到插件自身
if (typeof exec === "function") exec.call(jsp);
if (ext) ext.call(jsp);
} catch (ex) {
console.console('§6插件 §b%s §6执行 §d%s §6方法时发生错误 §4%s'.format(jsp.description.name, name, ex.message));
console.ex(ex);
}
}
}
@ -241,7 +234,7 @@ var plugins = [];
function init(path) {
var plugin = exports.$
if (plugin !== null) {
// 如果plugin不等于null 则代表是正式环境
// 如果plugin不等于null 则代表是正式环境
console.info("初始化 MiaoScript 插件系统: %s".format(plugin));
}
loadPlugins(path);
@ -262,7 +255,7 @@ function disable() {
};
function reload() {
checkAndRun(arguments, function (p) {
checkAndGet(arguments).forEach(function (p) {
disable(p);
p = loadPlugin(p.__FILE__);
load(p);

View File

@ -5,6 +5,9 @@ var File = Java.type("java.io.File");
var Files = Java.type("java.nio.file.Files");
var separatorChar = File.separatorChar;
var StandardCopyOption = Java.type("java.nio.file.StandardCopyOption");
var _toString = function (obj) {
return Object.prototype.toString.call(obj);
}
/**
* 用文件分割符合并路径
@ -24,10 +27,16 @@ function file() {
}
switch (arguments.length) {
case 1:
if (path(arguments[0])) {
return arguments[0];
var f = arguments[0]
if (f instanceof File) {
return f;
}
if (typeof f === "string") {
return new File(f);
}
if (f instanceof Path) {
return f.toFile();
}
return new File(arguments[0]);
case 2:
return new File(exports.file(arguments[0]), arguments[1]);
}
@ -73,14 +82,14 @@ function copy(inputStream, target, override) {
* 读取文件
* @param file 文件路径
*/
function read(file) {
f = file(file);
if (!f.exists()) {
console.warn('读取文件', f, '错误 文件不存在!');
function read(f) {
var file = exports.file(f);
if (!file.exists()) {
console.warn('读取文件', file, '错误 文件不存在!');
return;
}
// noinspection JSPrimitiveTypeWrapperUsage
return new java.lang.String(Files.readAllBytes(f.toPath()), "UTF-8");
return new java.lang.String(Files.readAllBytes(file.toPath()), "UTF-8");
};
/**
* 保存内容文件
@ -89,7 +98,9 @@ function read(file) {
* @param override 是否覆盖
*/
function save(path, content, override) {
Files.write(new File(path).toPath(),
var file = new File(path);
file.getParentFile().mkdirs();
Files.write(file.toPath(),
content.getBytes("UTF-8"),
override ? StandardCopyOption['REPLACE_EXISTING'] : StandardCopyOption['ATOMIC_MOVE']);
};
@ -116,6 +127,15 @@ function move(src, des, override) {
override ? StandardCopyOption['REPLACE_EXISTING'] : StandardCopyOption['ATOMIC_MOVE'])
};
function del(file) {
file = exports.file(file);
if (!file.exists()) { return; }
if (file.isDirectory()) {
Files.list(file.toPath()).collect(Collectors.toList()).forEach(function (f) { del(f); })
}
Files.delete(file.toPath());
}
exports = module.exports = {
canonical: path,
concat: concat,
@ -127,5 +147,6 @@ exports = module.exports = {
read: read,
save: save,
list: list,
move: move
move: move,
del: del
}

View File

@ -35,9 +35,13 @@
* 初始化模块
*/
function loadRequire() {
global.engineLoad = load;
global.load = function __denyGlobalLoad__() {
throw new Error('系统内部不许允许使用 load 如需执行脚本 请使用 engineLoad !');
}
// 初始化加载器
global.require = load(root + '/core/require.js')(root);
global.requireInternal = function (name) {
global.require = engineLoad(root + '/core/require.js')(root);
global.requireInternal = function requireInternal(name) {
return require(root + '/internal/' + DetectServerType + '/' + name + '.js');
}
}
@ -61,16 +65,16 @@
*/
function loadServerLib() {
var task = require('api/task');
global.setTimeout = function (func, time, _async) {
global.setTimeout = function setTimeout(func, time, _async) {
return _async ? task.laterAsync(func, time) : task.later(func, time);
};
global.clearTimeout = function (task) {
global.clearTimeout = function clearTimeout(task) {
task.cancel();
};
global.setInterval = function (func, time, _async) {
global.setInterval = function setInterval(func, time, _async) {
return _async ? task.timerAsync(func, time) : task.timer(func, time);
};
global.clearInterval = function (task) {
global.clearInterval = function clearInterval(task) {
task.cancel();
};
}
@ -95,9 +99,9 @@
/**
* 关闭插件Hook
*/
global.disable = function disable() {
global.engineDisable = function disable() {
if (global.manager && global.manager.$) {
global.manager.disable();
}
}
})(global);
})(global);

View File

@ -31,7 +31,7 @@
var File = Java.type("java.io.File");
var separatorChar = File.separatorChar;
var cacheDir = parent + separatorChar + "runtime";
var paths = [parent, parent + separatorChar + 'core', parent + separatorChar + 'modules'];
var paths = [parent, parent + separatorChar + 'core', parent + separatorChar + 'api', parent + separatorChar + 'modules'];
try{
base.delete(cacheDir);
@ -204,7 +204,7 @@
}
base.save(cacheFile, "(function __init__(module, exports, require, __dirname, __filename) {" + origin + "});");
// 使用 load 可以保留行号和文件名称
var compiledWrapper = load(cacheFile);
var compiledWrapper = engineLoad(cacheFile);
try {
base.delete(cacheFile);
} catch (ex) {
@ -260,11 +260,10 @@
};
}
// 判断是否存在 isFile 不存在说明 parent 是一个字符串 需要转成File
if (parent.isFile) {
if (typeof parent === "string") {
parent = new File(parent);
}
var cacheModules = [];
console.debug("初始化 require 模块组件 父目录 ", _canonical(parent));
return exports(parent);
});
});

View File

@ -47,7 +47,7 @@ function open(url, method, header) {
var conn = new URL(url).openConnection();
if (conn instanceof HttpsURLConnection) {
conn.setHostnameVerifier(TrustAnyHostnameVerifier);
conn.setSSLSocketFactory(TrustAnyHostnameVerifier);
conn.setSSLSocketFactory(SSLSocketFactory);
}
conn.setRequestMethod(method);
conn.setDoOutput(true);
@ -81,10 +81,12 @@ function request(url, method, header, params, body) {
var conn = open(buildUrl(url, params), method, header);
try {
conn.connect();
var out = conn.getOutputStream();
out.write(new String(body).getBytes(config.Charset));
out.flush();
out.close();
if (body) {
var out = conn.getOutputStream();
out.write(new String(body).getBytes(config.Charset));
out.flush();
out.close();
}
return response(conn);
} finally {
conn.disconnect();
@ -110,4 +112,4 @@ var http = {
}
})
exports = module.exports = http;
exports = module.exports = http;

View File

@ -27,7 +27,7 @@ function load() {
function enable() {
command.on(this, 'hello', {
cmd: function (sender, command, args) {
global.load(fs.file(root, 'test.js'));
engineLoad(fs.file(root, 'test.js'));
return true;
}
});
@ -49,7 +49,7 @@ function enable() {
function send(event, player){
// noinspection JSUnresolvedVariable
console.debug('玩家', player.name, "触发事件", event.class.simpleName);
console.debug('玩家', player.getName(), "触发事件", event.class.simpleName);
setTimeout(function () {
// noinspection JSUnresolvedVariable
player.sendMessage("§a欢迎来到 §bMiaoScript §a的世界! 当前在线: " + server.players.length)
@ -65,4 +65,4 @@ module.exports = {
load: load,
enable: enable,
disable: disable
};
};

View File

@ -0,0 +1,79 @@
'use strict';
/*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 http = require('http');
var fs = require('fs');
var nameMap = [];
var description = {
name: 'ItemTag',
version: '1.0',
author: '喵♂呜'
};
var itemConfig;
function load() {
var itemFile = self.file('item.yml');
if (!itemFile.exists()) {
base.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:
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:
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 getItemName(name) {
return itemConfig[(name + '').toUpperCase()] || name;
}
function bukkit(item , amount) {
var amounts = amount == 1 ? "" : "*" + amount;
item.setCustomName('§b' + getItemName(item.itemStack.type) + amounts);
item.setCustomNameVisible(true);
}
function sponge(entity) {
var itemOptional = entity.get(Keys.REPRESENTED_ITEM);
if (itemOptional.isPresent()) {
var item = itemOptional.get();
var amounts = item.count == 1 ? "" : "*" + item.count;
entity.offer(org.spongepowered.api.data.key.Keys.DISPLAY_NAME, org.spongepowered.api.text.Text.of('§b' + getItemName(item.type.name.split(':')[1]) + amounts));
entity.offer(org.spongepowered.api.data.key.Keys.CUSTOM_NAME_VISIBLE, true);
}
}
module.exports = {
description: description,
load: load,
enable: enable,
disable: disable
};