ARSpark
本帖最后由 RarityEG 于 2021-11-3 11:50 编辑

如果你听说过服务端插件,你的第一反应一定是:“好复杂啊!”特别是像 EssentialsX 这样的大型插件,功能多到一个配置文件都快装不下了。你可能会觉得:“反正我就用别人的插件就可以了,自己写…”于是就放弃了插件开发的梦想。

其实,你觉得插件开发遥不可及,并不是因为插件开发高深莫测,也不是因为插件开发技术超前,而是因为没有人告诉你那其实是一件很简单的事情

那么,如果再给你一次机会,让你跟着我一起学习插件开发,你愿意吗?

本教程就是基于此创建的。

教程入口:
https://plgdev.xuogroup.top
最近一次更新的说明:Done!Yeah!
完结撒花!
完结于:2021/2/28 13:26
(深夜还在疯狂码字……心疼心疼我的蹄蹄吧……嘤~)
这是
一份理论与实践结合的插件开发教程
从基础之基础,到终极之终极

阅读本教程几乎不需要基础!只要你能够熟练地操作电脑就可以了。
付费插件功能很强买不起?那有什么!我们自己来写一个!
Cloudflare Pages 实时更新:
https://plgdev.xuogroup.top

无法访问?也可以使用原始域名:https://plugin-diary.pages.dev

注意!

  • 站点使用 Docsify 呈现,首次访问较慢,第二次访问速度就会变快!
  • 教程内容非常紧凑,请仔细阅读!
  • GitHub Pages 在部分地区无法访问
  • EDIT: 本教程已改用 Cloudflare Pages,旧链接仍然有效哦~
  • (打不开请把你 DNS 改为 1.1.1.1 (iPv4) 或 2606:4700:4700::1111(iPv6))
  • (谁再说访问不了,我…我也不能把你怎么样

遇到问题?

那么大个回复按钮是看不见吗(不,要有耐心)请回复说明!

EDIT 2021/08/06: 有关本教程的 Discord 服务器已建立,欢迎来玩(可私信我获得邀请链接哦!)


洞穴夜莺
1,你没有对玩家聊天、受伤等事件作取消,这意味着如果op使用你的插件登录,其帐号将有风险。
2,
  1.    public static boolean verifyPassword(String playerName, String password) {
  2.         if (!isPlayerRegistered(playerName)) {
  3.             return false;
  4.         }
  5.         FileConfiguration config = HarmonyAuth.instance.getConfig();
  6.         return password.equals(config.getString(playerName.toLowerCase()));
  7.     }
复制代码

应该改为
  1.     public static boolean verifyPassword(String playerName, String password) {
  2.         return password.equals(config.getString(playerName.toLowerCase()));
  3.     }
复制代码




William_Shi
勘误:3-1中
Bukkit.createInventory 用于创建一个 Inventory,默认的物品栏就是箱子 GUI,所以无需修改。
括号内的三个参数分别是所属实体(这里通过服务端获取了一个名为「RarityEG」的玩家),大小(必须是 9 的倍数,从 1 * 9 到 9 * 9),标题。

请看CraftServer的代码

  1.   public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException {
  2.     Validate.isTrue((9 <= size && size <= 54 && size % 9 == 0), "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")");
  3.     return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title);
  4.   }
  5.   
复制代码


创造模式那个包括了全部物品的物品栏打开这件事是客户端行为,服务端管不着,因此不算
那么全游戏最大的物品栏是大箱子的54格,54=6*9
所以应当是 1*9 到 6*9

洞穴夜莺
你可以在 IDEA 中通过反编译查看这些类
何必呢,hub.spigotmc.org上有代码原文啊。


在服务端,UUID 的使用并不是很频繁,大多数情况下,使用玩家名就足以区分了。基本上能用 UUID 的地方也都能用名字代替。
区分玩家规范的方法是使用UUID,不然玩家改个名字就得联系服主挪数据。


Bukkit.getPlayer("Hacker").banPlayerFull("使用外挂被 Ban!");

我完全没找到这个方法。


顺便一提,上次我提出的两个问题你也没改啊

William_Shi
洞穴夜莺 发表于 2021-2-5 22:00
何必呢,hub.spigotmc.org上有代码原文啊。

不都在推荐uuid代替掉名字吗,而且uuid判断相等比字符串快

那个ban应该是要通过服务器的banlist
而且我记得好像文档说是ban要改动,但是事实上至今又没改动

ARSpark
洞穴夜莺 发表于 2021-2-5 22:00
何必呢,hub.spigotmc.org上有代码原文啊。

banPlayerFull 位于 https://papermc.io/javadocs/pape ... l-java.lang.String-
可能是因为我用的 Paper 吧,我会修正这部分
---
对上次内容的说明:
该部分内容是演示,并非实际应用,阅读到那一章时,读者可能才刚开始认识 EventHandler,这时候就不要把代码再搞复杂了(重点不一样)。
后面的一个项目中会重新关注这类问题

洞穴夜莺
RarityEG 发表于 2021-2-5 22:14
banPlayerFull 位于 https://papermc.io/javadocs/paper/1.16/org/bukkit/entity/Player.html#banPlayerF ...


ARSpark
洞穴夜莺 发表于 2021-2-5 22:20
  • 那么上次的第二个提议呢?
  • 请始终使用UUID辨识玩家

  • UUID的获取方法是啥来着查JavaDocs 查了半天没找到
    问题2已修复

    洞穴夜莺
    RarityEG 发表于 2021-2-5 22:30
    UUID的获取方法是啥来着查JavaDocs 查了半天没找到
    问题2已修复

    OfflinePlayer#getUniqueId()

    tdiant
    水群的时候突然发现,慕名而来

    哈哈哈哈
    package ... 语句根据你的包名而决定,直接照搬的后果我不负责!
    人  间  真  实

    打开以后直接点了NMS与反射那块,发现没写,挺可惜,GKD

    读起来的话总体感觉是没什么废话的,整体上无论是思路还是内容都是比较简单清晰的,
    我个人比较喜欢这种用实际事例举例的说明方法,
    个人感觉帖子里的“行动规划”式的写法挺有意思的,如果要是能写的促进读者边看边做的话,那内容会很成功

    其实我一直觉得设置Lib应该算Java基础(划掉

    不过确实有一些错误的地方,(整理完以后发现已经有dalao指出来了),好在整体内容是没什么错误的,
    有些地方个人觉得详略处理有些问题,比如插件的调试,从配置服务端开始讲,个人认为准备开发插件的话,应该具有开启一个服务端的能力才对
    新教程肯定有不足之处,多多改正就好,总之就是非常不错,加油

    洞穴夜莺
    Bukkit.getPlayer("RarityEG").setResourcePack("https\://example.com/xxx.zip");

    应为
    Bukkit.getPlayer("RarityEG").setResourcePack("https\\://example.com/xxx.zip");

    ARSpark
    截至 2/9 上午,累积的更新内容:站点有关的质量更新:


    3311373219
    我来踩你啦我正好在学习插件1可以教教我么

    RPT001
    正好打算重新开始学mc的插件开发
    上次来mcbbs开发板块的时候翻到的教程都是远古时期的玩意了
    感谢楼主的教程~

    CaveNightingale
    这里有两个事情我没看懂,我觉得在教程里还需要解释一下
    1,为啥要监听受伤事件然后取消而不是直接给玩家设置无敌?设置无敌免除伤害的同时能够使玩家失去对怪物的吸引能力,不是更好?
    2,为啥IDataManager每次都要重新new?

    ARSpark
    本帖最后由 RarityEG 于 2021-2-10 21:23 编辑
    CaveNightingale 发表于 2021-2-10 21:07
    这里有两个事情我没看懂,我觉得在教程里还需要解释一下
    1,为啥要监听受伤事件然后取消而不是直接给玩家设 ...

    第一个是我没想到的……
    第二个主要是用静态类的话执行每次操作前都要做一个 if 判断选择使用哪一个 Manager,多写代码,另外由于类变量和方法都不会复制,不怎么占用内存(稍微加重了GC的负担,不过应该都能 YoungGC 掉),主要是为了展示接口的用法。
    另:设置无敌用哪个方法?改游戏模式?

    CaveNightingale
    本帖最后由 CaveNightingale 于 2021-2-10 22:29 编辑
    RarityEG 发表于 2021-2-10 21:21
    第一个是我没想到的……
    第二个主要是用静态类的话执行每次操作前都要做一个 if 判断选择使用哪一个 Manag ...

    说实话我是在Mod开发里学会设置无敌的,根据注释好像是这个,但是看实现又不太像
    你可以试试,用NMS肯定能搞

    用NMS搞的话就简单粗暴,你可以自己找一下有没有Bukkit封装
    EntityPlayer#abilities.isInvulnerable = true;
    EntityPlayer#updateAbilities();
    如果实在没有那就算了吧


    第二个问题是
    为啥不
    public static final IDataManager IDM;
    static {
            if(......)
                    IDM = new IDataManagerImplementA();
            else if(.......)
                    IDM = new IDataManagerImplementB();
            else
                    IDM = new IDataManagerImplementC();
    }
    然后后面直接用IDM.xxxx

    ARSpark
    CaveNightingale 发表于 2021-2-10 22:21
    说实话我是在Mod开发里学会设置无敌的,根据注释好像是这个,但是看实现又不太像
    你可以试试,用NMS肯定能 ...

    是 Bukkit 多线程的问题,如果全局只用一个对象可能访问冲突掉
    不然就得 synchronized 同步,而和数据库的交互又比较慢,同步执行下一个线程迟迟无法访问
    所以就new 一个出来
    虽然也没有从根本上解决问题(static 的 username, password),但这些类变量的同步锁时间比一整个DataManager的同步锁时间短很多,而且除了loadAll是写其它都是读,基本不存在访问冲突

    tdiant
    CaveNightingale 发表于 2021-2-10 21:07
    这里有两个事情我没看懂,我觉得在教程里还需要解释一下
    1,为啥要监听受伤事件然后取消而不是直接给玩家设 ...

    在Bukkit中对玩家设置无敌是有现成的API的,LivingEntity的setNoDamageTicks与setMaximumNoDamageTicks,这个还是根据个人喜好来的,我感觉用的更多的还是直接监听然后取消掉

    IDataManager的问题我也想吐槽 2333

    13576500959
    前排观摩大佬

    逍遥先生.
    感谢楼主的分享,对我很有帮助

    lkpkp
    ohhhhhh表示感谢!!!

    洞穴夜莺
    建议5-6中加入bungee api与原始Json文本之间的对应关系
    这样看这更清晰,没那么累
    https://wiki.biligame.com/mc/%E5 ... C%E6%A0%BC%E5%BC%8F

    wujinghao233

    ?????
    这不是大佬吗

    膜拜膜拜!

    埃拉拉
    这么详细的教程,竟然没有优秀 =.=,像 NMS 之类的讲解可是很少有的

    洞穴夜莺
    本帖最后由 洞穴夜莺 于 2021-2-26 20:10 编辑

    提个小建议
    糟糕的copy to clipboard遮挡


    接下来是个大问题
    https://plgdev.xuogroup.top/#/7-4
    方法上的synchronized锁住的是整个对象
    这一点可以通过jshell运行如下内容来验证
    1. class X {
    2.         synchronized void block() {
    3.                 try{
    4.                         Thread.sleep(10000);
    5.                 }catch(Throwable ex){}
    6.         }
    7.         synchronized int test() {
    8.                 return 1;
    9.         }
    10. }

    11. var k = new X();// 创建X对象
    12. k.test();// 返回1
    13. new Thread(() -> k.block()).start();// 在另外的线程调用block方法
    14. k.test();// 明显观察到这里会卡住
    复制代码

    whatfilmae
    感谢楼主分享!

    叽叽小石头
    零基础学习插件哦猴,先收藏起来

    天佑--冬天
    这个是真的不戳 nb

    闪鑫呦
    哇哇哇哇

    your123
    超级棒!!!

    zhangyi6666
    6666666666

    zhangyi6666
    666666666666666

    zhangyi6666
    6666666666666

    zhangyi6666
    6666666666666666

    zhangyi6666
    66666666666666

    Cool_Loong
    大佬你好,这是我看你的教程写登陆插件的事件监听类,我在运行插件的时候玩家不登陆还是可以打开物品栏,但是我明明已经按照你的教程监听并且取消这个事件了,请你看看我哪里出错了
    1. public class EventListener implements Listener {
    2.     @EventHandler
    3.     public void onPlayerLogin(PlayerLoginEvent event) {
    4.         LoginData.addPlayerName(event.getPlayer().getName());
    5.     }
    6.     @EventHandler
    7.     public void onPlayerQuit(PlayerQuitEvent event){
    8.         LoginData.removePlayerName(event.getPlayer().getName());
    9.     }
    10.     @EventHandler
    11.     public void cancelMove(PlayerMoveEvent event){
    12.         cancelLoggedIn(event);
    13.     }
    14.     @EventHandler
    15.     public void cancelInteract(PlayerInteractEvent event){
    16.         cancelLoggedIn(event);
    17.     }
    18.     @EventHandler
    19.     public void cancelInteractAtEntity(PlayerInteractAtEntityEvent event){
    20.         cancelLoggedIn(event);
    21.     }
    22.     @EventHandler
    23.     public void cancelPortal(PlayerPortalEvent event){
    24.         cancelLoggedIn(event);
    25.     }
    26.     @EventHandler
    27.     public void cancelTeleport(PlayerTeleportEvent event){
    28.         cancelLoggedIn(event);
    29.     }
    30.     @EventHandler
    31.     public void cancelOpenInventory(InventoryOpenEvent event){
    32.         cancelLoggedIn(event);
    33.     }
    34.     @EventHandler
    35.     public void noHurt(EntityDamageEvent event){
    36.         if ((event).getEntity() instanceof Player){
    37.             if (LoginData.hasPlayerName((event).getEntity().getName())){
    38.                 event.setCancelled(true);
    39.             }
    40.         }
    41.     }
    42.     public static void cancelLoggedIn(Cancellable event){
    43.         if (event instanceof PlayerEvent) {
    44.             if (LoginData.hasPlayerName(((PlayerEvent) event).getPlayer().getName())) {
    45.                 event.setCancelled(true);
    46.             }
    47.         } else if (event instanceof InventoryOpenEvent){
    48.             if (LoginData.hasPlayerName(((InventoryOpenEvent) event).getPlayer().getName())){
    49.                 event.setCancelled(true);
    50.             }
    51.         }
    52.     }
    53.     }
    复制代码

    ARSpark
    本帖最后由 RarityEG 于 2021-3-12 20:40 编辑
    wyd1542536763 发表于 2021-3-12 15:31
    大佬你好,这是我看你的教程写登陆插件的事件监听类,我在运行插件的时候玩家不登陆还是可以打开物品栏,但 ...

    主类中是否进行了注册?
    1. public void onEnable() {
    2.     Bukkit.getPluginManager().registerEvents(new EventListener(), this);
    3. }
    复制代码


    EDIT:请参见楼上回答(我没有实地测试)

    洞穴夜莺
    wyd1542536763 发表于 2021-3-12 15:31
    大佬你好,这是我看你的教程写登陆插件的事件监听类,我在运行插件的时候玩家不登陆还是可以打开物品栏,但 ...

    打开物品栏是纯客户端行为,bukkit无法监听

    Glom_
    针灸零基础 支持一波

    oNvsulep
    感谢楼主分享!

    Elekoay
    真的用心了 大制作!!

    184817451
    感觉很强的样子。

    天佑--冬天
    简直大爱 太nb了

    woodp1anks~
    最开始的那个选JAR文件不应该是选开服之后服务器根目录里的cache文件夹里的patched吗?我就因为这个弄了好久直到看见你的示范图像为止

    ARSpark
    本帖最后由 RarityEG 于 2021-3-14 21:10 编辑
    wood-man 发表于 2021-3-14 17:37
    最开始的那个选JAR文件不应该是选开服之后服务器根目录里的cache文件夹里的patched吗?我就因为这个弄了好久 ...

    因为你用的是 Paper
    GetBukkit 得到的 Spigot 就是 spigot-1.16.5.jar

    woodp1anks~
    RarityEG 发表于 2021-3-14 21:05
    因为你用的是 Paper
    GetBukkit 得到的 Spigot 就是 spigot-1.16.5.jar

    建议加一句,不然真的是麻烦

    123+123=246
    牛牛牛牛牛牛牛牛牛牛

    NBTmc
    感谢大佬的教程,开发插件真的很有用!!!

    qq2633700353
    牛牛牛牛牛牛牛牛牛牛

    下一页 最后一页