企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
>此篇幅可能较大,我尽可能的讲的直白一点 导航: [TOC] <br><br><br> ***** # 浅尝代码(必读) ## 在pr4.3.0以下版本时,公式是这样的 ``` #启用伤害节点 damage-types: #攻击mob时所选 mob: #左键攻击 damage: #自定义伤害类型 伤害: true #战斗公式节点 formula: fight: mob: damage: 伤害: damage: hit: true value: 'Math.max({a.攻击}-{v,防御},1)' ``` ***** <br><br><br> ## 因此转换后得到: ``` #启用伤害节点 damage-types: #攻击mob时所选 mob: #左键攻击 damage: #自定义伤害类型 伤害: true #战斗公式节点 formula: fight: mob: damage: 伤害: damage: hit: true value: |- UMap a = data.get("attacker"); Att a_att = a.get("att"); UMap v = data.get("victim"); Att v_att = v.get("att"); double a_攻击 = a_att.get("攻击"); double v_防御 = v_att.get("防御"); return Math.max(a_攻击-v_防御,1); ``` ***** <br><br><br> ## UMap类型 ``` UMap a = data.get("attacker"); //类型 变量名 = 变量名.方法名(参数1[,参数2...]); ``` **UMap** 是一个java类 ~~(类似于HashMap)~~ ,内含了 **get(String key)** 等方法供用户去获取里面的内容 >用白话来讲就`UMap`相当于一个柜子,柜子有很多格子,pr在传过来的会在格子里面塞满了东西 你要拿柜子上 **第三排第四格(假如是 attacker)** 的东西 只需要使用 **data.get("attacker");** 即可拿到 ***** <br><br><br> ## 获取双方相关属性对象: ``` UMap a = data.get("attacker"); //先拿到攻击方的相关数据,并命名变量名为 a(供下方代码使用), 攻击方的相关数据也是一个 UMap ``` ``` Att a_att = a.get("att"); //再从 a 里面拿到 攻击方的相关属性对象 类型 Att 并命名变量为: a_att(供下面代码使用) ``` **[`Att`](../%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E7%B1%BB/%5B%E5%B1%9E%E6%80%A7%E5%AF%B9%E8%B1%A1%5DAtt.md)** 是pr封装的一个属性类 ``` //拿到 攻击方 的 "攻击" 总属性: double a_攻击 = a_att.get("攻击"); // (注意如果是 String 字符串类型常量,需要前后加上双引号) //注意这里是 double 类型, 也就小数, 整数类型一般用 int ``` ``` //再拿到 被攻击方 的 "防御" 属性 UMap v = data.get("victim"); // 被攻击方的相关数据 Att v_att = v.get("att"); //被攻击方的相关属性对象 double v_防御 = v_att.get("防御"); ``` ``` //最终将两者属性运用在想要的公式当中返回给pr return Math.max(a_攻击 - v_防御, 1); //这里的 Math.max(参数1,参数2) 是java的取最大值的静态方法,pr已将其自动导入环境,无需再导入可直接使用 ``` <br><br><br> ## 总和起来的代码就是 ``` UMap a = data.get("attacker"); UMap v = data.get("victim"); Att a_att = a.get("att"); Att v_att = v.get("att"); double a_攻击 = a_att.get("攻击"); double v_防御 = v_att.get("防御"); return Math.max(a_攻击 - v_防御, 1); //注意代码的放置顺序, 在下面的代码可以调用到上面代码声明的变量, //如: Att a_att = a.get("att"); 就必须放在 UMap a = data.get("attacker"); 的下面 //说白点就是: //你得先告诉程序, 我声明了一个类型为 UMap ,名字为 a 的变量 //并且他拿着我给他的重要文件,我下面可能要用到,别给老子搞丢了 ``` >[danger] Tip: 变量名不可以重复!!!, 支持 英文或下划线或中文/开头,后面还支持数字 > 变量在程序中是没有任何作用的, 只是给我们写代码的人看得懂不要出错就行 <br><br><br> ## 启用公式返回值和战斗公式中hit和value的返回值 ``` #启用伤害节点 damage-types: mob: damage: 伤害: true //这里的返回值是boolea型(真假|对错), 其值只有2个 不是 true 就是 false #战斗公式节点 fight-formula: mob: damage: 伤害: damage: hit: true //这里命中公式返回的是 boolean 型(真假), 其值只有2个 不是 true 就是 false //这里如果写了多行代码,则需要写成 return true/false; 如果只有单个 true 或者 false 可以直接写 true/false /* 如下 hit: |- UMap a = data.get("attacker"); Att a_att = a.get("att"); UMap v = data.get("victim"); Att v_att = v.get("att"); double a_命中= a_att.get("命中"); double v_闪避 = v_att.get("闪避"); //这里的 Math.random() 是取随机数小数 范围 0~1 if(Math.random() <= a_命中 / (a_命中+v_闪避)){ return true; } else { return false; } */ value: |- UMap a = data.get("attacker"); Att a_att = a.get("att"); UMap v = data.get("victim"); Att v_att = v.get("att"); double a_攻击 = a_att.get("攻击"); double v_防御 = v_att.get("防御"); return Math.max(a_攻击-v_防御,1); //这里返回的是 double 型(小数),值的范围就大了 ``` <br><br><br> ## 在公式中进行调试输出任意内容 ``` #战斗公式节点 fight-formula: mob: damage: 伤害: damage: hit: true value: |- //使用java原生方法向服务端后台打印信息 double a_属性 = 15D; System.out.println("这是调试信息, 打印攻击方属性: " + a_属性); ``` >可以使用 + 来连接各种变量或者字符串 <br><br><br> # 深入一点(浅尝后) >[danger] 请在学习浅尝后正确触发伤害公式再看 <br><br><br> ## 拿到双方的等级 ``` UMap a = data.get("attacker"); UMap v = data.get("victim"); //既然 攻击方 也是个 UMap , 那里面应该也有不少东西 int a_level = a.get("level"); //除了能 获取 Att 属性, pr也将当前的主职业等级(或者是mob的当前等级) 也放进来了 int v_level = v.get("level"); //被攻击方也是一样 ``` <br><br><br> ## 万能的 IF 表达式(用于流程控制) ``` //万能的if写法 if( 逻辑表达式 ){ //成立的代码块 }else{ //不成立的代码块 } ``` <br><br><br> ***** ### 逻辑表达式可以是啥: >逻辑表达式一旦成立,就会返回 true #### 比较两个数 ``` 比较两个数: x == y x != y x >= y x > y x < y x <= y ``` <br><br><br> #### 比较两个字符串 ``` String str = "我是SB"; //以下是原生java的方法 str.contains("SB") //str中包含 "SB" 时就会成立, str.equals("SB") //str 完全等于 "SB" 时就会成立 str.equalsIgnoreCase("sb") //str 忽略大小写等于 "sb" 时就会成立 ``` <br><br><br> #### 比较逻辑类型 ``` boolean 玩家等级是否大于10 = false; if(玩家等级是否大于10 == true){ ... } else { ... } //注意判断符号不是 = 而是 == if(玩家等级是否大于10){ ... } else { ... } //当上面的代码判断 boolean 是否等于 true 时,可忽略后面的 等值判断代码 ``` <br><br><br> #### 取反 >前面加个叹号,使其逻辑意思相反,假的变真,真的变假 如果是多元表达式, 请前后加上括号 ``` 2>1 = true !(2>1) = false String str = "SB"; !str.equalsIgnoreCase("sb") //str 忽略大小写等于 "sb" 时会成立,但是取反, 就返回 false 了 ``` <br><br><br> #### 比较双方等级 ``` UMap a = data.get("attacker"); UMap v = data.get("victim"); int a_level = a.get("level"); int v_level = v.get("level"); if(a_level > v_level){ //如果攻击方的等级 > 被攻击方的等级时, 执行的代码块 }else{ //如果攻击方的等级 <= 被攻击方的等级时, 执行的代码块 } ``` <br><br><br> ### 多个同级逻辑判断 && 和 || >&& = 而且 || = 或者 例子1: ``` ... int a_level = 10; int v_level = 5; //攻击方等级> 9级 而且 攻击方等级 > 被攻击方等级 这个表达式成立 if(a_level > 9 && a_level > v_level){ //成立 } ... ``` 例子2: ``` ... int a_level = 5; int v_level = 3; //攻击方等级> 9级 或者 攻击方等级 > 被攻击方等级 这个表达式成立 if(a_level > 9 || a_level > v_level){ //成立 } ... ``` 例子3: ``` ... int a_level = 15; int v_level = 3; //在攻击方等级 >= 10 而且 <= 20 并且 > 被攻击方等级时成立 if(a_level >= 10 && a_level <= 20 && a_level > v_level){ //成立 } ... ``` <br><br><br> ### 简化if/三元运算 ``` ... int a_level = 10; int v_level = 5; double 伤害倍率 = 1; //先声明一个变量,名字随便取,自己看得懂就行 if(a_level > v_level){ 伤害倍率 = 1.5; }else{ 伤害倍率 = 0.5; } ... ``` 当if代码块里面只有一行代码时, 可以去掉花括号,结果如下: ``` ... int a_level = 10; int v_level = 5; double 伤害倍率 = 1; //先声明一个变量,名字随便取,自己看得懂就行 if(a_level > v_level) 伤害倍率 = 1.5; else 伤害倍率 = 0.5; //或者=> if(a_level > v_level) 伤害倍率 = 1.5; else 伤害倍率 = 0.5; ... ``` 像上面这种代码还可以简化成三元运算 ``` ... int a_level = 10; int v_level = 5; double 伤害倍率 = a_level > v_level ? 1.5 : 0.5; //跟上面的结果是一样的 ... ``` 逻辑型变量更简单: ``` ... int a_level = 10; int v_level = 5; boolean 伤害倍率开启 = a_level > v_level; //三元运算 问号 前面的是逻辑表达式 ... ``` <br><br><br> ### if的注意事项 >[danger]在公式中或者具有返回值的方法体里面 所有if分支都需要具有一个明确的返回值 例如命中公式: ``` hit: |- if( 逻辑表达式1 ){ if( 逻辑表达式2 ){ return true; } else { return false; } } else if( 逻辑表达式2 ){ return true; } else { return false; } ``` <br><br><br> ## Math数学函数类 >[info] 该类是java自带的原生类: `java.lang.Math` <br><br><br> ### 常用的方法 ``` Math.max(x,y); //取2者较大值 Math.min(x,y); //取2者较小值 Math.round(x); //四舍五入, 返回 int 类型 Math.random(); //取0~1之间的随机小数, 返回 double 类型 Math.pow(x,y); //取 x 的 y 次方 , 返回 double 类型 Math.sqrt(x); //开平方 , 返回 double 类型 ``` 其他的方法可以查看: [java原生文档](https://docs.oracle.com/javase/8/docs/api/) <br><br><br> ## Pr 往 UMap data 放了些什么? ### **UMap** 类的所有方法 ``` boolean has(String key); //是否有 key 的内容 Object get(String key); //获取 key 的内容 Object get(String key, Object def); //获取 key 的内容,没找到时 返回 def set(String key, Object obj); //设置 key 的内容为 obj Set keys(); //返回所有 key ``` >这一段是给有点基础的人看的,不懂可以不深究 这里出现了 **Object** 类,这是java所有类的父类,也可以理解为 通用类型 ``` UMap a = data.get("attacker"); //这里之所以不需要向下强转,是因为我在代码层面提供了运行时检查类型 UMap a = (UMap) data.get("attacker"); //实际上这才是java调用的正确方法 ``` <br><br><br> ### for循环,遍历UMap的所有内容 ``` for(int i = 0 ; i < 10; i++){ System.out.println(i); //这里会循环10次, 并输出在控制台当前的循环次数 } ``` ``` int 循环次数= 10; for(int i = 0 ; i < 循环次数; i++){ System.out.println("当前循环次数: " + i); } ``` **UMap** 有个方法是: ``` Set keys(); //返回所有 key ``` 可使用for循环来遍历里面的内容: >[danger] 注意! 代码并不支持泛型,请勿使用泛型 ``` Set set = data.keys(); for(String key : set){ Object obj = data.get(key); System.out.println("data里面有: " + key + " ,它的java类型为: " + obj.getClass().getName()); } ``` <br><br><br> ***** ### 旧版暴击伤害内的 {this.damage.value} 怎么获取 [img] ![](https://img.kancloud.cn/89/04/8904b96b793c04fd76e9ab564aa81e80_851x274.png) ![](https://img.kancloud.cn/57/fc/57fc5b919f01fe33caecb181d3f4516f_757x75.png) 这个也是pr添加的,类型也是 UMap 这个里面的内容会在pr把一段代码执行完,添加对应的内容 下图是获取 **伤害**(自定义类型) 下 **damage**(普通伤害) 的 **value**(计算的值) 并对其做一些计算 ![](https://img.kancloud.cn/f2/73/f273945f82876e344bb84390be1e7062_892x675.png) <br><br><br> ***** ### 旧版上次数据列表 ``` double total_damage = fight_data.get("total_damage",0);// 到此为止的总伤害 double last_伤害_total_damage = fight_data.get("last_伤害_total_damage",0); //上面 伤害 的总结果(包含crit等),可用于附加伤害 boolean last_伤害_damage_hit = fight_data.get("last_伤害_damage_hit",false); //上面 伤害.damage 的 命中结果 ,可用于附加伤害的命中结果 double last_伤害_damage_value = fight_data.get("last_伤害_damage_value",0); //上面 伤害.damage 的 计算结果 ,可用于附加伤害的比例结果 ``` <br><br><br> ***** ## SkillAPI技能等级现在该怎么获取? 当战斗公式隶属于 **skillapi** 类型时 可使用以下代码获取当前释放技能的等级 ``` int skill_level = fight_data.get("skill_level"); ``` 另外如果像做技能等级加成伤害时,可使用下列代码: ``` UMap a = data.get("attacker");//先获取 攻击方的 UMap USkill a_skill = a.get("skillapi"); //获取 攻击方的 USkill 数据(必须安装skillapi才会存在) int a_天雷斩_level = a_skill.level("天雷斩"); //获取 攻击方 "天雷斩" 的技能等级 //被攻击方同理 return [最终伤害...] * (1+a_天雷斩_level*0.05); //即可实现每级天雷斩提升 5% 的伤害 ``` **USkill** 所有方法: ``` int level(String skill); //获取技能等级, 没有则返回0 boolean hasSkill(String skill); //是否有某个技能 ``` ### 技能等级加成伤害的完整例子: ``` UMap a = data.get("attacker"); UMap v = data.get("victim"); Att a_att = a.get("att"); Att v_att = v.get("att"); USkill a_skill = a.get("skillapi"); int a_天雷斩_level = a_skill.level("天雷斩"); //这里这里 double a_攻击 = a_att.get("攻击"); double v_防御 = v_att.get("防御"); return Math.max(a_攻击 - v_防御,1) * (1+a_天雷斩_level*0.05) * Math.random(0.8,1.2); //最后可以加一个 80%到120% 的结果随机值以免每次伤害都是一样 QAQ ``` <br><br><br> # 再深一点!!! >[danger] 可能需要一些java基础才能理解,下面有一些已经封装好可以直接用的类 <br><br><br> ## 向java类添加的额外方法/新增自定义类及方法 在服务器目录 `plugins\PxRpg\Modules\Code\Cover` 文件夹下 可以创建自定义类文件(后缀名必须为: .java) >*如果找到java类,会在原有的java类上添加新的方法,未找到则会新建一个java类(看看即可)* ![](https://img.kancloud.cn/b2/b4/b2b41a3dabb99904d0893c279a738052_659x108.png) <br> ### :-: **pxrpg.工具.java** ``` import org.bukkit.entity.Player; import me.clip.placeholderapi.PlaceholderAPI as 变量; //导入PAPI变量的api 并设置别名为 变量 class 工具{ static void 调试(String text){ System.out.println(text); } static double 到小数(String text){ return Double.parseDouble(text); } /** 此方法需要安装 PlaceholderAPI 插件 */ static String 取变量(String text){ return 取变量((Player) null,text); } /** 此方法需要安装 PlaceholderAPI 插件 */ static String 取变量(AdapterPlayer player,String text){ return 取变量((Player) player.getObject(),text); } /** 此方法需要安装 PlaceholderAPI 插件 */ static String 取变量(Player player,String text){ switch (text){ case "%player_name%": return player.getName(); case "%plauer_level%": return player.getLevel()+""; } return 变量.setPlaceholders(player, text); } } ``` <br><br><br> ### 将自定义类/其他类导入到环境中 ***** >[danger]创建的自定义类不会自动导入到环境内,可以在 `plugins\PxRpg\Modules\Code\env-imports.yml`文件里面添加 ![](https://img.kancloud.cn/0d/29/0d29b9b6fa140a97a072dc28a4d969c1_169x47.png) 这样就可以直接在pr任何地方直接调用 `工具.方法名()`了 #### 不导入环境可以在使用前使用 `import pxrpg.工具;` 语句来导入: ``` UMap a = data.get("attacker"); import pxrpg.工具; AdapterLivingEntity a_lving = a.get("entity"); String a_name = 工具.取变量(a_lving, "%player_name%"); ... ``` #### 或者直接使用全路径名`pxrpg.工具.方法名()` 来调用 ``` UMap a = data.get("attacker"); AdapterLivingEntity a_lving = a.get("entity"); String a_name = pxrpg.工具.取变量(a_lving, "%player_name%"); ... ``` #### 如果后台提示重名了, 可以使用 `as 别名` 来定义另外的名字 ![](https://img.kancloud.cn/53/cd/53cdec86780a4f2e5a00a1aced9e233f_247x61.png) 这样可以用 `工具类别名.方法名()` 来调用 <br><br><br> ### :-: **java.lang.Math.java** ***** ``` import java.text.NumberFormat; import java.math.RoundingMode; /** 在 java.lang.Math 类添加扩展方法(类似于kotlin的扩展方法) */ class Math{ /** 取随机小数,含头含尾 min = 最小值 max = 最大值 */ static double random(double min,double max){ /* 在当前类的静态方法体内是具有静态环境的 这种情况下,下列三种静态方法的调用都是等价的: random() Math.random() java.lang.Math.random() */ return random() * (max-min)+min; } /** 取随机整数,含头含尾 min = 最小值 max = 最大值 */ static int random(int min,int max){ //注意强转 int return (int) round(random() * (max-min) + min); } /** 取随机整数,含头含尾 max = 最大值 */ static int random(int max){ //注意强转 int return random(0,max); } /** 兼容旧版randomint */ static int randomint(int min,int max){ return random(min,max); } //静态成员 static NumberFormat nf = NumberFormat.getNumberInstance(); /** 保留小数后的几位 value = 小数 retain = 保留位数 */ static double decimals(double value,int retain){ nf.setMaximumFractionDigits(retain); nf.setRoundingMode(RoundingMode.HALF_UP); nf.setGroupingUsed(false); return Double.parseDouble(nf.format(value)); } /** 兼容旧版RandomWeight */ static String RandomWeight(int[] nums,String[] rets){ int max = 0; for(int i = 0;i<nums.length;i++) max+=nums[i]; int next = random(max); int c = 0; for(int i = 0;i<nums.length;i++){ c += nums[i]; if(c >= next) return rets[i]; } return rets[0]; } } ``` <br><br><br> ## 现在可以利用 pxrpg.工具 在战斗公式获取双方的papi变量 ### 例子: ``` UMap a = data.get("attacker"); UMap v = data.get("victim"); AdapterLivingEntity a_living = a.get("entity");//获取攻击方实体 AdapterLivingEntity v_living = v.get("entity");//获取被攻击方实体 double a_papi_level = 0;//先声明 a papi等级 为0 double v_papi_level = 0;//先声明 v papi等级 为0 //判断实体是不是 玩家类型, 加上这一步可以在所有战斗公式通用 //不加的话 player攻击mob里面的mob是没有papi的 if(a_living instanceof AdapterPlayer) a_papi_level = 工具.到小数(工具.取变量(a_living ,"%player_level%")); if(v_living instanceof AdapterPlayer) v_papi_level = 工具.到小数(工具.取变量((AdapterPlayer) v_living ,"%player_level%"));//这里的强转其实可以省略 ... ``` > **`instanceof`** 是java关键字,用于判断对象是不是某个类型,返回 **`boolean`** 型 <br><br><br> ### 利用三元运算简化例子: ``` UMap a = data.get("attacker"); UMap v = data.get("victim"); AdapterLivingEntity a_living = a.get("entity");//获取攻击方实体 AdapterLivingEntity v_living = v.get("entity");//获取被攻击方实体 double a_papi_level = a_living instanceof AdapterPlayer ? 工具.到小数(工具.取变量(a_living ,"%player_level%")) : 0; double v_papi_level = v_living instanceof AdapterPlayer ? 工具.到小数(工具.取变量(v_living ,"%player_level%")): 0; ... ``` <br><br><br> ## 提取相同的代码到公用库类 **Code\Cover** [img] 上面讲了新建自定义类 可以新建一个自己看得懂的类 ![](https://img.kancloud.cn/db/15/db15185a6b1718d115d8f603b3a713c1_682x121.png) 并且在 `Code/env-imports.yml` 里面添加到环境导入 ![](https://img.kancloud.cn/b3/a7/b3a79f53662b2a2ce42dfd17128168b3_280x81.png) ![](https://img.kancloud.cn/b1/d1/b1d1f37c1c817fe7403ac2f42a542d8c_694x653.png) 打开职业文件找到普通攻击的公式内容改为以下内容即可 ![](https://img.kancloud.cn/a1/da/a1da928fe07ae31129330b070933ba34_581x204.png) 当执行代码时,只有一行可以省略 **return** 这样可以将相同的代码简化成一个方法,并可在其他地方也这样调用