脚本的可共用部 分
脚本的出处
语法和js类似,又能基本使用java的所有方法。
建议先看jexl教程:
1.JEXL – Apache Commons JEXL Syntax
2.JEXL – Apache Commons JEXL Examples
翻译版:
第三方文档
脚本的优势
- 可以随意调用java内的任意API,代码风格和JS基本可以平替降低学习成本。
- 甚至可以直接调用客户端Forge的API,例如获得玩家所在世界的天气,所在地区的群系,周围怪物的数量来实现想要达到的效果。
- 经过二次开发拥有更详细的错误提示和语法检测。
- 支持代码补全,开发方便,前后端可完全分离。
脚本的API
数据类型转换
| 操作 | 代码 |
|---|---|
| 转为字符串 | str(obj) |
| 转为int | int(obj) |
| 转为long | long(obj) |
| 转为float | float(obj) |
| 转为boolean | bool(obj) |
| 转为double | double(obj) |
可直接静态调用的API
| 方法介绍 | 方法参数 | 方法案例 | 版本 |
|---|---|---|---|
| 设置客户端标题 | Utils.setClientTitle(str) | Utils.setClientTitle("haha"+new Date().toString()) | |
| 获得客户端标题 | Utils.getClientTitle() | var title = Utils.getClientTitle() | |
| 获得客户端当前tick次数 | Utils.getTick() | ||
| 判断客户端是否打开了萌芽界面 | Utils.hasOpenGermScreen() | ||
| 判断客户端是否打开了界面 | Utils.hasOpenScreen() | ||
| 获取客户端的HudProxy(和GuiProxy类似) | Utils.getHudProxy() | Utils.getHudProxy().runDos("message<->555) | 版本>4.4.2 |
| 设置客户端按键激活状态 | Utils.setKeyBindActive(KeyType,bool) | 可以模型玩家按下W向前移动等,需要在startTick时调用才有效 | |
| 在客户端发给玩家一句话 | Log.chat(str, obj...) | Log.chat('今天{}也{}了', '你', '迟到'); | |
| 获取当前时间戳 | Utils.now(); | ||
| 设置物品显示冷却效果 | Utils.setItemCooldown(item,tick); | ||
| 获取物品冷却 | var tick = Utils.getItemCooldown(item); | ||
| 获取玩家当前所在世界时间 | Utils.getWorldTime() | Log.chat('范围{}类型{}', '[0-24000]', 'double'); | |
| 获取容器内悬浮的组件 | Utils.getHoverPart(Container) | Utils.getHoverPart(GuiScreen) 可能返回null | |
| 打印某个实例或者类中的方法 | Utils.printMethods(obj) | Utils.printMethods(GuiProxy) | |
| 判断按键是否按下 | Utils.isKeyDown(KeyType); | Utils.isKeyDown(Utils.getKeyType("1")); | |
| 判断物品是否是空气 | Utils.isAir(itemStack); | Utils.isAir(slot.getItemStack()) | |
| 获得鼠标悬浮的物品 | Utils.getHoverItem() | var item = Utils.getHoverItem(); | |
| 获得物品的lore | Utils.getItemLore(itemstack) | var list=Utils.getItemLore(Utils.getHoverItem()) | |
| 获得物品的名字 | Utils.getItemName(itemstack) | var name=Utils.getItemName(Utils.getHoverItem()) | |
| 设置物品的Lore | Utils.setItemLore(list) | Utils.setItemLore(Arrays.asList("哈哈哈")) | |
| 设置物品的名字 | Utils.setItemName(str) | Utils.setItemName("哈哈哈) | |
| 获得物品的NBT | Utils.getItemNbt(itemStack,str) | var str = Utils.getItemNbt(Utils.getHoverItem(),'PxRpg.data.sell.money'); | |
| 设置物品的NBT | Utils.setItemNbt(itemstack,str,str) | Utils.setItemNbt(Utils.getHoverItem(),'PxRpg.display',"你好") | |
| 获得一个类,方便调用静态方法 | Utils.forClass('java.lang.Double') | var dclazz = Utils.forClass('java.lang.Double'); dclazz.parseDouble('99.99'); | |
| 新建一个物品 | Utils.newItem('type','displayName','lore') | Utils.newItem('DIAMOND_SWROD','卍开 天锁斩月',Arrays.asList('第一行','第二行')) | |
| 根据Identity获取物品 | Utils.getIdentityItem(str) | 首先在服务端调用GermPacketAPI.sendSlotItemStack('chajianming_test',ItemStack)然后脚本内调用Utils.getIdentityItem('chajianming_test') | |
| 获得游戏窗口像素宽度 | Utils.getPixelWidth() | 游戏窗口的像素宽度,和变量w结果一样 | |
| 获得游戏窗口像素高度 | Utils.getPixelHeight() | 游戏窗口的像素高度,和变量h结果一样 | |
| 获得游戏窗口高宽 | Utils.getWindowWidth() Utils.getWindowHeight() | 游戏窗口真实宽高,此数值一般比wh大 | |
| 获 得鼠标位置X | Utils.getMouseX() | 鼠标位置X | |
| 获得鼠标位置Y | Utils.getMouseY() | 鼠标位置Y | |
| 计算公式 | Utils.calc(str) | Utils.calc('w*20'); | |
| 获得KeyType | Utils.getKeyType(str) | 从按键名获取Utils.getKeyType("A") | |
| 获得KeyType | Utils.getKeyType(int) | 从ID获取Utils.getKeyType(0) | |
| 发送left hud msg | Utils.sendLeftMsg(str) | Utils.sendLeftMsg('哈哈哈哈'); | |
| 发送center hud msg | Utils.sendCenterMsg(str) | Utils.sendCenterMsg('哈哈哈哈'); | |
| 发送anchor hud msg | Utils.sendAnchorMsg(str,str) | 需要在服务端Config.yml内配置好anchorUtils.sendAnchorMsg('anchor1','哈哈哈哈'); | |
| 去除颜色 | Utils.stripColor(str) | Utils.stripColor('§a哈哈'); | |
| 将字符串转为map | Utils.parseMap(str) | 转换格式为Utils.parseMap('path=textures/gui.png;width=20;height=20') | |
| 将json字符串转为map | Utils.jsonToMap(str) | var map = Utils.jsonToMap('{"formatVersion":3,"atlasSize":25}') | |
| 延迟tick执行方法 | Utils.runTimeoutTick(JexlContext,jexlMethod,tick,args...) | 例子 var test = (txt,txt2)=>{Log.chat("text "+txt+" "+txt2);};Utils.runTimeoutTick(JexlContext,test,10,"我是tickTimeout","哈哈哈"); | |
| 延迟毫秒执行方法 | Utils.runTimeout(JexlContext,jexlMethod,tick,args...) | 点击查看更详细例子 客户端事件监听 | |
| 异步延迟tick执行方法 | Utils.runTimeoutTickAsync(JexlContext,jexlMethod,tick,args...) | 点击查看更详细例子 客户端事件监听 | |
| 异步延迟毫秒执行方法 | Utils.runTimeoutAsync(JexlContext,jexlMethod,tick,args...) | 点击查看更详细例子 客户端事件监听 | |
| 脚本调度器 | var scheduler = Utils.getScheduler() | 点击查看详情 客户端事件监听 | |
| 删除所有字符替换图标的字符 | Utils.stripFontReplacer(str) | ||
| 获取所有导航 | Utils.getNavigates() | 返回list | |
| 排序list | Utils.sort(list,(o1,o2)=>{return o1-02;}) 或 Utils.sort(JexlContext,list,(o1,o2)=>{return o1-02;}) | var list=[3,4,2,1];Utils.sort(JexlContext,list,(o1,o2)=>{return o1-02;}); | |
| 防抖节流工具 | 点击查看 防抖节流工具 | 版本>=4.4.2-8 |
变量相关的API
| 方法介绍 | 方法参数 | 方法案例 | 版本 |
|---|---|---|---|
| 解析字符串中的变量值,如有不存在值的变量就请求服务端 | PAPI.request(str); | var str = PAPI.request('界面是:%germ_gui_open%'); | |
| 解析字符串中的变量值 | PAPI.parse(str); | var str = PAPI.request('界面是:%germ_gui_open%'); | |
| 字符串中是否存在变量 | PAPI.has(str); | var bool = PAPI.has('有变量%germ_gui_open%'); | |
| 放入变量值,一段时间未使用会过期 | PAPI.put(str, str); | PAPI.put('%germ_gui_open%', 'true'); | |
| 放入变量值,不会过期,除非手动移除变量 | PAPI.put2(str, str); | PAPI.put2('%germ_gui_open%', 'true'); | |
| 放入变量值到客户端缓存(客户端重启后,也可使用%cache_变量%直接读取到值),不会过期,除非手动移除变量 | PAPI.put3(str, str); | PAPI.put3('%变量%', 'true'); | |
| 删除变量 | PAPI.remove(str); | PAPI.remove('%germ_gui_open%'); | |
| 向服务端请求变量的值 | PAPI.refresh(str) | PAPI.refresh("player_name") or PAPI.refresh("%player_name%") 可以用来快速刷新变量的值 | |
| 异步请求服务端变量并执行回调(单个变量) | Utils.reqPapi(JexlContext, str) | Utils.reqPapi(JexlContext, "%variable%").then((response)=>{Log.chat("变量值:"+response.value);}); | 版本>=4.4.2-9 |
| 异步请求服务端变量并执行回调(多个变量) | Utils.reqPapi(JexlContext, Map) | Utils.reqPapi(JexlContext, {"var1":"%variable%","var2":"variable2"}).then((response)=>{Log.chat("响应:"+response);}); | 版本>=4.4.2-9 |
| 异步请求服务端变量并执行回调(指定目标) | Utils.reqPapi(JexlContext, Map, target) | Utils.reqPapi(JexlContext, {"var1":"%player_name%"}, "target_player_name").then((response)=>{Log.chat("响应:"+response);}); | 版本>=4.4.2-9 |
共享数据相关的API,可以用来插件A调用插件B的数据,或者多个界面之间传递数据,或者暂储存一些数据,当玩家退出服务器会清空globalMap(在代理内切换服务器不会清理)
| 方法介绍 | 方法参数 | 方法案例 |
|---|---|---|
| 客户端公用Map | Utils.putGlobal(str,obj) | Utils.putGlobal('germpass',map) |
Utils.getGlobalMap() | ||
Utils.getGlobal(str) | var obj = Utils.getGlobal('germpass') | |
Utils.removeGlobal(str) | ||
Utils.clearGlobal() | ||
| 缓存入客户端GermCache | Utils.saveCache(str,obj) | Utils.saveCache('str',map) |
| 取出缓存入客户端的内容 | Utils.getCache(str,class) | Utils.getCache('str',Utils.forClass('java.util.Map')) |
一些示例
//创建一个Map
var mapsUtils = Utils.forClass('com.google.common.collect.Maps');
var map = mapsUtils.newHashMap();
//创建一个List
var listUtils = Utils.forClass('com.google.common.collect.Lists');
var list = listUtils.newArrayList();
var list2 = new("java.util.ArrayList");
//mcp搜索网站 https://mcp.thiakil.com/#/search
//获得Forge的Minecraft实例
var minecraft = Utils.forClass('net.minecraft.client.Minecraft').func_71410_x();
//获得Forge的客户端玩家实例
var player = minecraft.field_71439_g;
//遍历map
for (let entry of map.entrySet()) {
let key = entry.getKey();
let value = entry.getValue();
}
//遍历list
for(let entry of list){
}
// 参考 GuiScreen.post 的使用方式
GuiScreen.post(JexlContext, 'g_germ/get_money', {
"debug": true,
"money_type": "铜币"
}).then((responseContent) => {
Log.chat(`获取成功 ${responseContent["money"]}`);
});
// 单个变量请求
Utils.reqPapi(JexlContext, "%player_name%").then((response) => {
Log.chat("玩家名称: " + response.value);
}).catch((error) => {
Log.chat("请求失败: " + error.getMessage());
});
// 多个变量请求
Utils.reqPapi(JexlContext, {
"playerName": "%player_name%",
"playerLevel": "%player_level%",
"playerMoney": "%player_money%"
}).then((response) => {
Log.chat("响应数据: " + response);
// response 包含所有请求的变量数据
});
// 指定目标请求
Utils.reqPapi(JexlContext, {
"targetData": "%some_variable%"
}, "target_player_name").then((response) => {
Log.chat("特定目标响应: " + response);
});
注意事项
1.在方法内部不要写return; 用return 0;来替代
var b= tickCount % 4 ;
if(b != 0){
return 0;
}
2.注意保持方法有序,只有在上面声明的方法,在下面才可以调用。 3.不要使用jexl的关键字、操作符、静态变量作为变量名(var size = 666;),如size,str,double,new等,更多请查看上面的jexl文档。 4.如果是直接在yml的脚本,建议使用/*我是注释*/ 双斜杠 //我是注释 可能无效 5.槽位组件只能在服务端创建,在客户端脚本里创建无法点击和显示物品(除非自己完成逻辑,例如使用Utils里面创建物品并设置上)。 6.在vscode中开发时for(let switchLine : switches) vscode会报错,可以写成for(let switchLine of switches)
在GermPlugin/jexl中添加脚本
在GUI中导入jexl脚本
建议
- 文件以.js结尾可以使用vscode的一些补全比较方便
导入到GUI中使用: 例如有个GUI的组件名为button_click_next,那么可以创建一个ButtonUtils.js文件里面添加一个 方法名为 button_click_next_clickScript的方法 那么点击这个按钮就可以触发这个脚本 。
// ButtonUtils.js文件 放置 GermPlugin/jexl中
//这个方法相当于gui配置中的 options.script.methods
function options_methods() {
Log.chat("6666");
}
//相当于 options.script.openScript
function options_openScript(){
Log.chat("7777");
}
//点击界面内的 button_click_next 的按钮就会执行下面的内容
function button_click_next_clickScript() {
Log.chat("click");
}
function options_keyScript$keydown_D() {
Log.chat("按下D");
}
function options_keyScript$keydown_A() {
Log.chat("按下A");
}
# gui.yml 文件 放置 GermPlugin/gui中
测试脚本:
options:
#注意后面的;
scriptjs: |
@import ButtonUtils.js;
button_click_next:
type: button
defaultPath: textures/misc/logo.png
hoverColor: 0xAAAAAAAA
width: 50
height: 50
在脚本内导入包
在脚本中创建对象
其中obj为map类型,图片来自https://pxpmc.com/作者老何

更多使用方法查看下面的开发示例
在GUI内使用脚本
脚本的配置案例
ScriptGui:
options:
#一般用来做脚本处理时的数据,填写json
data: |
{
num:10,
}
#variables字段,例如下方的示例,则可以在该ui内的字段里直接使用 {{a}} {{c.d}} 来获取variable内的值
variables: |
{
a:1,
c:{
d:3
}
}
#比较推荐使用此方案使用脚本 使用js后缀 可利用js文件的代码补全
#更多使用详情请看下方的开发示例
#脚本资源放入服务端GermPlugin/jexl目录下
scriptjs: |
@import main1.js; #省略 GermPlugun/jexl路径
#可在界 面中嵌入文本 但是无法使用补全
script:
#嵌入该界面的脚本内容
methods: |
var abc = (x,y)->{
return x+y+10;
}
#循环执行
#每次更新界面都会重新执行一次脚本 默认最多一秒执行60次(最小次数由玩家客户端帧率决定)
updateScript: |
Log.chat('cycle '+updateCount);
#tick执行 一秒20次
tickScript: |
Log.chat('tick '+tickCount);
#脚本初始化的时候执行
openScript: |
Log.chat('open');
#收到服务端下发的数据会触发一次
dataScript: |
Log.chat('data');
#脚本卸载的时候执行
closeScript: |
Log.chat('close');
#滚轮的时候执行
scrollScript: |
Log.chat('滚动了 '+scrollChange);
#滚动之前进行 支持return true;return false; 会阻止滚动框滚动
scrollPreScript: |
Log.chat('滚动了 '+scrollChange);
return true;
#服务端响应脚本 使用流程为 从客户端主动请求服务端 `GermScreen.post('germplugin_test1',{});`
#然后在服务端重写GermGuiScreen方法`void onReceivePost(String path, Map<String, Object> contentMap, Map<String, Object> responseMap)`
#在responseMap写入内容客户端就会响应germplugin_test1
responseScript:
germplugin_test1: |
Log.chat('回应消息 '+responseContent.toString());
Log.chat('客户端data '+GuiOptions.getData().toString());
#按键脚本
keyScript:
keyDown@U: |
Log.chat('按下按键 '+str(keyType));
keyUp@K: |
Log.chat('松开按键 '+str(keyType));
#按键脚本写法2
keyDownScript: |
Log.chat('按下按键 '+str(keyType));
keyUpScript: |
Log.chat('松开按键 '+str(keyType));
脚本触发器
界面内支持的脚本触发
| 脚本名称 | 脚本触发时机 | 脚本内独有变量 |
|---|---|---|
| tickScript | 客户端每次tick时触发一次 | tickCount:代表当前的tick次数 |
| updateScript | 客户端每帧渲染时触发一次 | updateCount:代表update执行的次数 |
| openScript | 界面打开时触发 | |
| dataScript | 收到服务端下发的数据触发 | |
| closeScript | 界面关闭时触发 | |
| resizeScript | 窗口大小变化时触发(4.4.1+) | |
| scrollScript | 滚动滚轮时触发 | scrollChange:代表当次滚动的距离 |
| scrollPreScript | 滚动之前进行 支持return true;会阻止滚动框滚动 | scrollChange:代表当次滚动的距离 |
| responseScript | 收到服务端的数据时触发 | |
| keyScript | 按下指定按键时触发 | keyType:代表按下的按键 |
openScript,dataScript都会在打开界面的时候响应一次。 打开界面之后,服务端再调用options.setData(Map) 就只会响应 dataScript 了,注意这里setData传入的只能是map
除了脚本内独有变量,调用以上script(界面内的script)还可以使用预设的变量 GuiOptions,GuiScreen,GuiProxy,JexlContext
JexlContext 为一个map,map的key为变量名,val为变量值,可以打印map获得执行改脚本时的所有入参变量
组件内支持的通用脚本触发
| 脚本名称 | 脚本触发时机 | 脚本独有变量 |
|---|---|---|
| tickScript | 客户端每次tick时触发 | tickCount:代表当前的tick次数 |
| initScript | 首次加载组件时触发 | |
| closeSciprt | 组件被卸载时触发(被remove | 界面关闭) |
除了脚本内独有变量,调用以上script(组件内的Script)还可以使用预设的变量GuiOptions,GuiScreen,GuiProxy,GuiPart
按钮组件支持的脚本触发
| 脚本名称 | 脚本触发时机 | 脚本独有变量 |
|---|---|---|
| clickScript | 鼠标左键点击触发 | |
| clickReleaseScript | ||
| rightScript | ||
| rightReleaseScript | ||
| hoverScript | ||
| leaveScript | ||
| shiftClickScript | ||
| shiftClickReleaseScript | ||
| shiftRightScript | ||
| shiftRightReleaseScript | ||
| middleScript | 鼠标中间点击触发 | |
| shiftMiddleScript | 按下shift并鼠标点击触发 |
输入框组件支持的脚本触发
| 脚本名称 | 脚本触发时机 | 脚本独有变量 |
|---|---|---|
| enterScript | 按下回车触发 | input |
| inputScript | 输入字符触发 | input |
| focusScript | 触发聚焦触发 | focus: bool 聚焦时为true 失去聚焦时为false |
复选框组件支持的脚本触发
| 脚本名称 | 脚本触发时机 | 脚本独有变量 |
|---|---|---|
| clickScript | 鼠标左键点击触发 | |
| rightScript | ||
| hoverScript | ||
| leaveScript | ||
| checkedScript | ||
| cancelledScript |
slot组件支持的脚本触发
| 脚本名称 | 脚本触发时机 | 脚本独有变量 |
|---|---|---|
| shiftClickScript | ||
| clickScript | ||
| shiftRightScript | ||
| rightScript | ||
| shiftMiddleScript | ||
| middleScript | ||
| hoverScript | ||
| leaveScript |
调用脚本都可以使用变量ScriptName来获得当前脚本的名字
界面内的API
界面内预设的变量和预设变量内的方法
| 变量名称 | 变量方法 | 方法介绍 |
|---|---|---|
| GuiOptions | getData() | 获得服务端下发的数据,会以map的形式存在 |
| get... | 有和服务端Options类中相同的get方法 | |
| set... | 有和服务端Options类中相同set方法 | |
| setVariable(key,val) | 修改或添加变量的值例如修改{{c.d}}的值 GuiOptions.setVariable('c.d','10') | |
| clearVariable() | 清空所有变量 | |
| removeVariable(key) | 删除一个变量的值 GuiOptions.removeVariable('c.d'); | |
| getVariable(key) | 获得一个变量的值 | |
| GuiPart | getCacheMap() | 获得组件内的缓存Map |
| get... | 有和服务端GermGuiPart类中相同的get方法 | |
| set... | 有和服务端GermGuiPart类中相同set方法 | |
| getPreset() | GuiPart.getPreset("locationX"),获得配置中预设的值(如果直接getLocationX()获得是数值) | |
| 坐标操作 | var number = getLocationX/Y/Z() | 获得组件的绝对坐标位置 |
| setLocationX/Y/Z(number丨str) | 设置组件的相对坐标 | |
| var number = getRelativeX/Y/Z() | 获得组件的相对坐标位置 | |
| setRelativeX/Y/Z(number丨str) | 设置组件的相对坐标 | |
| var number = getAbsoluteX/Y/Z() | 获得组件的绝对坐标 | |
| setAbsoluteX/Y/Z(number丨str) | 设置组件的绝对坐标 | |
| GuiScreen | getCacheMap() | 获得界面内的缓存Map |
| ... | 有和服务端GermGuiScreen内相同的方法 | |
| getWidth() | 获得options内设置的width | |
| getHeight() | 获得options内设置的height | |
| runResponseScript(String scriptPath, Map responseContent) | 执行指定response脚本 | |
| getScreenLevel() | 返回字符串 PARENT,CHILD | |
| getScreenType() | 返回字符串 GUI,HUD,EFFECT_GUI | |
| post(path, json) | GuiScreen.post('tower/used',{'used':used,'towerId':towerMap.get('towerId')}); | |
| close() | 关闭界面 | |
| GuiProxy | runDos(str) | 立刻执行dos会阻塞程序至dos执行结束 |
| runDos(strlist) | ||
| analyzeDos(str) | 在下一帧执行dos | |
| analyzeDos(strlist) | ||
| var guiscreens = getGuiScreens() | ||
| var guisceen = getParentGuiScreen() | ||
| getGuiScreen("guiname") | ||
| init() | 重新初始化 | |
| runResponseScript(String guiName, String scriptPath, Map responseContent) | 执行指定gui中的指定response脚本 |
界面内组件的拓展方法(除了字段默认的get和set以外)
| 组件类型 | 方法 | 方法介绍 |
|---|---|---|
| label | getVisibleTexts() | 获得界面正式渲染的文本(被spilitWidth,应用变量后等后处理过的文本) 返回一个List<String> |
| label | setText(str) | 设置字符串支持\n换行 |
可调用的静态方法
| 方法介绍 | 方法参数 | 方法案例 |
|---|---|---|
| 生成组件-根据模版 | GuiHelper.newPart(configSrc,indexName,guiContainer) | GuiHelper.newPart(towerTemplate.getSrc(),'tower'+tower.get('towerId'),towersContanier); |
| 生成组件重载方法-根据类型新建一个组件 | GuiHelper.newPart(type,indexName,guiContainer) | GuiHelper.newPart('canvas','tower'+tower.get('towerId'),towersContanier); |
| 生成组件重载方法2-根据模板和变量 | GuiHelper.newPart(type,indexName,guiContainer,map<string,string>) | GuiHelper.newPart('canvas','tower'+tower.get('towerId'),towersContanier,{'{towerName}':'无敌','{towerLevel}':'100'); |
配置示例1
以下为利用脚本实现的功能性界面 >>>自定义玩家状态HUD
开发示例1
以下为可开源的部分界面代码,以嵌入的方式实现了,根据options中的数据生成界面,在点击按钮时操作界面等操作。

界面实现代码
- 界面介绍
- 界面配置
- 服务端代码
以直接在界面配置中嵌入脚本的方式实现了,根据options中的数据生成界面,在点击按钮时操作界面等操作。
editor:
options:
startX: w/2-1920/3/2
startY: h/2-1080/3/2
blur: true
script:
methods: |
var guiCacheMap = GuiScreen.getCacheMap();
var dataMap = GuiOptions.getData();
var SetSelect = (towerCanvas)->{
var towerMap = towerCanvas.getCacheMap().get('tower');
var has = towerMap.get('has');
var button = towerCanvas.getGuiPart('tower_button');
towerCanvas.getGuiPart('tower_entity')
.setTooltip(towerMap.get('tooltip'));
if(!has){
button.setDefaultPath('textures/gui/tower/button_lock.png');
button.setHoverPath('textures/gui/tower/button_lock.png');
}else{
button.setDefaultPath('textures/gui/tower/button_normal.png');
button.setHoverPath('textures/gui/tower/button_hover.png');
}
var sel = towerMap.get('used');
if(!has){
button.setTexts(Arrays.asList('§7§l锁定'));
button.setTooltip(Arrays.asList('§a未解锁'));
}else if(sel){
button.setTexts(Arrays.asList('§c§l取消'));
button.setTooltip(Arrays.asList('§a点击取消选择',
'§a防御塔到期: '+towerMap.get('expiredDate')));
}else{
button.setTexts(Arrays.asList('§e§l选择'));
button.setTooltip(Arrays.asList('§a点击选择',
'§a防御塔到期: '+towerMap.get('expiredDate')));
}
}
var GetSelectCount = ()->{
var towerScroll = GuiScreen.getGuiPart("towers")
var towers = towerScroll.getGuiParts();
var count = 0;
for(let towerCanvas:towers){
var towerMap = towerCanvas.getCacheMap().get('tower');
var sel = towerMap.get('used');
if(sel){
count++;
}
}
return count;
}
dataScript: |
var towerTemplate;
if(!guiCacheMap.containsKey('towerTemplate')){
towerTemplate = GuiScreen.getGuiPart("towers$tower");
guiCacheMap.put('towerTemplate', towerTemplate);
GuiScreen.removeGuiPart("towers$tower");
}else{
towerTemplate = guiCacheMap.get('towerTemplate');
}
var towersContanier = GuiScreen.getGuiPart("towers");
for(let tower: dataMap.get("towers")){
var ttower = GuiHelper.newPart(towerTemplate.getSrc(),
'tower'+tower.get('towerId'),towersContanier);
ttower.getCacheMap().put('tower',tower);
var background = ttower.getGuiPart('tower_background');
var newbackgroundpath = background.getPath()
.replace('{rare}',str(tower.get('rare')));
background.setPath(newbackgroundpath);
var towerentity = ttower.getGuiPart('tower_entity');
towerentity.setName(tower.get('modelName'));
SetSelect(ttower);
towersContanier.addGuiPart(ttower);
}
bg:
type: texture
path: textures/gui/tower/shop_bg.png
width: 1920/3
height: 1080/3
towers:
type: scroll
locationX: "57"
locationY: "72"
width: "565"
height: "240"
invalidV: false
scrollableV: "460"
relative: true
layout:
type: FLOW
gapX: "24"
gapY: "48"
scrollableParts:
tower:
type: canvas
width: 256/3
height: 384/3
relativeParts:
tower_background:
type: texture
path: textures/gui/tower/rare_{rare}.png
width: 256/3
height: 384/3
tower_button:
type: button
defaultPath: textures/gui/tower/button_normal.png
hoverPath: textures/gui/tower/button_hover.png
locationY: "384/3+2"
width: 256/3
height: 128/3
textOffsetY: "-4"
texts:
- "§a§l选择"
clickScript: |
var towerMap = GuiPart.getParentPart().getCacheMap().get('tower');
var has = towerMap.get('has');
if(!has){
return null;
}
var used = !towerMap.get('used');
if(used){
var usedCount = GetSelectCount();
if(usedCount>=dataMap.get('maxUsed')){
Log.chat('已达到最大可用防御塔数量');
return null;
}
}
towerMap.put('used',used);
SetSelect(GuiPart.getParentPart());
if(towerMap.get('has')){
GuiScreen.post('tower/used',
{'used':used,'towerId':towerMap.get('towerId')});
}
tower_entity:
type: entity
locationX: "44"
locationY: "95"
size: "32"
name: "{tower_name}"
model: "armorstand"
followCursor: false
lookAtX: "44"
lookAtY: "40"
animations:
rotate:
type: 1
rotateY: 360
cycle: -1
duration: 3000
money_label:
type: label
locationX: "320"
locationY: "15"
align: "center"
scale: "1.5"
texts:
- "§f【防御塔卡组编辑】 §a可用防御塔{{selectable_tower_amount}}/{{max_tower_amount}}"
close:
type: button
defaultPath: textures/gui/tower/close.png
hoverColor: 0xFF888888
locationX: "594"
locationY: "2"
width: 128/3
height: 128/3
clickDos:
- open<->null
public class MainGuiWindow extends GermGuiScreen {
static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final TowerPlayer towerPlayer;
public MainGuiWindow(ConfigurationSection src, TowerPlayer towerPlayer) {
super("tower_lobby_main", src);
this.towerPlayer = towerPlayer;
this.refreshVariable();
this.refreshData();
}
@Override
public void onReceivePost(String path, Map<String, Object> contentMap, Map<String, Object> responseMap) {
super.onReceivePost(path, contentMap, responseMap);
if ("tower/used".equals(path)) {
String towerId = (String) contentMap.get("towerId");
boolean used = (Boolean) contentMap.get("used");
TowerService towerService = TowerLobby.getInstance().getTowerService();
if (used) {
towerService.useTower(towerPlayer.getPlayer(), towerId);
} else {
towerService.unusedTower(towerPlayer.getPlayer(), towerId);
}
refreshVariable();
}
}
public void refreshVariable() {
GermGuiOptions options = getOptions();
Variables variables = new Variables();
TowerService towerService = TowerLobby.getInstance().getTowerService();
int maxTower = TowerLobby.getInstance().getSetting().getMaxTower();
variables.setMax_tower_amount(maxTower);
variables.setSelectable_tower_amount(towerService.getTowerSelectedCount(towerPlayer.getPlayer()));
options.setVariables(variables);
}
private void refreshData() {
Setting setting = TowerLobby.getInstance().getSetting();
PlayerData playerData = towerPlayer.getPlayerData();
GermGuiOptions options = getOptions();
OptionData optionData = new OptionData();
optionData.setMaxUsed(TowerLobby.getInstance().getSetting().getMaxTower());
for (TowerBean towerBean : setting.getTowers()) {
OptionDataEntry optionDataEntry = new OptionDataEntry();
if (playerData.hasTower(towerBean.getTowerId())) {
TowerEntry towerEntry = playerData.getTowerEntry(towerBean.getTowerId());
optionDataEntry.setTowerId(towerBean.getTowerId());
optionDataEntry.setRare(towerBean.getRare());
optionDataEntry.setModelName(towerBean.getModelName());
optionDataEntry.setUsed(towerEntry.getUsed() == 1);
optionDataEntry.setHas(true);
optionDataEntry.setExpiredDate(dateFormat.format(towerEntry.getExpireTime()));
optionDataEntry.setTooltip(towerBean.getLore());
} else {
optionDataEntry.setTowerId(towerBean.getTowerId());
optionDataEntry.setRare(towerBean.getRare());
optionDataEntry.setModelName(towerBean.getModelName());
optionDataEntry.setUsed(false);
optionDataEntry.setHas(false);
optionDataEntry.setTooltip(towerBean.getLore());
}
optionData.getTowers().add(optionDataEntry);
}
optionData.getTowers().sort((o1, o2) -> {
if (o1.isHas() && !o2.isHas()) {
return -1;
} else if (!o1.isHas() && o2.isHas()) {
return 1;
} else {
return o1.getRare() - o2.getRare();
}
});
options.setData(optionData);
}
@lombok.Data
@Accessors(chain = true)
private static class Variables {
int selectable_tower_amount;
int max_tower_amount;
}
@Data
@Accessors(chain = true)
private static class OptionData {
int maxUsed;
List<OptionDataEntry> towers = Lists.newArrayList();
}
@Data
@Accessors(chain = true)
private static class OptionDataEntry {
String towerId;
String modelName;
boolean used;
boolean has;
int rare;
String expiredDate;
List<String> tooltip;
}
开发示例2

