上一节 下一节
第二章 第二节 事件相关方法
参与编写者: MayflyOvO
建议学习时间: 40分钟
学习要点: 重点记忆 Event 下各种方法的使用。要实际去编写。光看不写是记不起来的!
本章目录
一、前言
本章我并不想单纯的去讲某个Event下面都有OOO方法,看了这么多章,我们来上手实现一个登录插件吧。通过这个插件来 深入浅出 学习各种Event的使用。
带有 :red_circle: 符号需要重点记忆理解
二、Nukkit 插件初步配置
关于Nukkit插件如何开始开发,已经由@Magic Lu同学在前面讲过了,此处不再赘述。
Talk Is Cheap, Show You The Code
plugin.yml
1 2 3 4 5 6
| name: site.dotdream.loginplugin main: site.dotdream.LoginPlugin version: "1.0" author: "MayflyOvO" api: ["1.0.9"] description: A Simple Auth Plugin
|
LoginPlugin.java(程序入口)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package site.dotdream;
import cn.nukkit.Player; import cn.nukkit.plugin.PluginBase; import site.dotdream.handlers.AuthEventHandler;
import java.util.ArrayList; import java.util.List;
public class LoginPlugin extends PluginBase { private static LoginPlugin plugin; private List<Player> loginPlayerList = new ArrayList<>();
@Override public void onEnable() { plugin = this;
this.getServer().getPluginManager().registerEvents(new AuthEventHandler(), this); }
@Override public void onDisable() {
}
public boolean isPlayerLogin(Player player) { return loginPlayerList.contains(player);
}
public void playerLogin(Player player) { this.loginPlayerList.add(player); }
public void playerLogout(Player player) { this.loginPlayerList.remove(player); }
public static LoginPlugin getInstance() { return plugin; } }
|
handlers/AuthEventHandler.java
1 2 3 4 5 6 7
| package site.dotdream.handlers;
import cn.nukkit.event.Listener;
public class AuthEventHandler implements Listener { private LoginPlugin plugin = LoginPlugin.getInstance(); }
|
添加Package
命名
创建类
可以看到我们定义了一个列表loginPlayerList
来记录已经登录的玩家,同时创建了isPlayerLogin
,playerLogin
,playerLogout
方法,来获取是否登录,以及操作该列表
然后我们又注册了一个Event。为了代码简洁方便维护,我建立了一个Package并将它独立放到一个叫AuthEventHandler的类里。同时又在onEnable中将它注册,使其生效。:wink:
到这里插件的初步配置就完成了,接下来我们大部分操作都在这个AuthEventHandler.java中…
三、从玩家加入开始…
我们在制作插件的时候,有必要认真的考虑一下插件的逻辑。
以本教程将要编写的登录插件为例,想要实现一个 登录前 --> 输入密码 --> 登录后
,那么势必要判断:
- 某玩家登录过了吗?
- 没有登陆?
- 那么我就
禁止取消 你:走动,放置与破坏,伤害与被伤害,饥饿值变化,与其他玩家聊天。
为什么这里说是取消而不是禁止?
因为当玩家进行这些活动,Nukkit询问我们事先注册的Event
Nukkit:你!插件LoginPlugin,对这个动作有什么意见吗?
没有插件的情况下,这些动作都是会被同意的,通过一个重要的方法event.setCancelled()
告诉Nukkit
LoginPlugin:我不同意!你要取消掉本次活动
1 2 3 4 5 6
| 伪代码如下
如果 isPlayerLogin(玩家) 等于 否: Event设置为取消 否则: 不设置取消,即是允许
|
这些都是为了实现我们的目标而需要考虑在内的
好的,脑海中有了这些概念,我们来实际编写吧!
向AuthEventHandler.java中加入如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer();
event.setJoinMessage("");
plugin.getServer().getScheduler().scheduleDelayedTask(plugin, () -> { player.sendTitle("欢迎来到OOO服务器", "请先登录", 20, Integer.MAX_VALUE, 20); }, 2 * 20); }
|
这里涉及了几个方法,我们来分开说:
- :red_circle:event.getPlayer() 很常用的方法,也很重要,通过它,我们可以获得这个动作(Event)的发起者。
- :red_circle:event.setJoinMessage() 这是一个PlayerJoinEvent独有的方法。通过它,我们可以设置这名玩家加入服务器后,发送一条向全服广播某玩家已加入游戏的消息
- player.sendTitle() 这个方法可以向玩家发送两行几乎占满全屏的字,它接受5个参数:(第一行字,第二行字,渐入的时间,显示的时间,渐出的时间),需要注意的是,Nukkit使用红石刻计算时间,也就是20刻=1秒钟。将显示的时间设为Integer变量的最大值,让其一直显示,直到取消。同时这里还涉及一个Nukkit的BUG,如果直接在玩家加入时发送Message或者Title,玩家是不显示的,所以使用
DelayedTask
做了2秒延迟
这样我们就对新加入的玩家发送了一个要求登录的提示。
那取消未登录玩家的放置破坏啥的在哪呢?
别急!我们这就来~
四、我不许你动 你就不能动
继续向AuthEventHandler.java中加入如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @EventHandler public void onPlayerMove(PlayerMoveEvent event) { Player player = event.getPlayer();
if (!plugin.isPlayerLogin(player)) { Location from = event.getFrom(); Location to = event.getTo(); if (from.getX() != to.getX() || from.getZ() != to.getZ()) { event.setCancelled(); } } }
|
同样的方法不再解释,我们主要来看 :red_circle:event.getFrom() 与 :red_circle:event.getTo()
这两个方法为PlayerMoveEvent特有的方法,并且都能够得到一个类型为Location的变量,区别是from为玩家移动前的位置,to为玩家移动后的位置
Location类型变量包含4个值得注意的方法
1 2 3 4
| getX() getY() getZ() getDirectionVector()
|
所以,当我们判断玩家的X,Z其中任何一个值不相等,就可以:red_circle:event.setCancelled()
来告诉Nukkit,我不批准!这样玩家此次的移动就会被取消。
同理,大部分的Event都可以通过setCancelled()
来取消。但是例如PlayerQuitEvent
便不可以,因为你没有办法阻止玩家离开服务器。
在这里需要大家思考,为什么不
1 2 3 4 5 6 7
| if(!to.equals(from)){ event.setCancelled(); }
if (from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ()){ }
|
这样会有什么缺点?
五、禁止其余动作
以下的代码禁止了:
- 放置与破坏
- 玩家模型碰撞
- 玩家饥饿值变化
它们相比之前并没有什么特殊,无非是检查是否登录,判断是否取消,我直接放代码在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
@EventHandler public void onBlockPlace(BlockPlaceEvent event) { if (!plugin.isPlayerLogin(event.getPlayer())) { event.setCancelled(); } }
@EventHandler public void onBlockBreak(BlockBreakEvent event) { if (!plugin.isPlayerLogin(event.getPlayer())) { event.setCancelled(); } }
@EventHandler public void onPlayerInteractEvent(PlayerInteractEvent event) { if (!plugin.isPlayerLogin(event.getPlayer())) { event.setCancelled(); } }
@EventHandler public void onPlayerFoodLevelChangeEvent(PlayerFoodLevelChangeEvent event) { if (!plugin.isPlayerLogin(event.getPlayer())) { event.setCancelled(); } }
|
除以上几个Event外,存在2个需要大家重点看的Event
- 玩家伤害及受伤害
- 与其他玩家聊天
先上代码吧…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
@EventHandler public void onEntityDamage(EntityDamageEvent event) { Player player = null; if (event.getEntity() instanceof Player) { player = (Player) event.getEntity(); } else if (event instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) event).getDamager(); if (damager instanceof Player) player = (Player) damager; } else if (event instanceof EntityDamageByChildEntityEvent) { Entity damager = ((EntityDamageByChildEntityEvent) event).getDamager(); if (damager instanceof Player) player = (Player) damager; }
if (player != null && !plugin.isPlayerLogin(player)) { event.setCancelled(); } }
|
这个EntityDamageEvent比较特殊,它没有 getPlayer()
方法,因为它是一个涵盖的很广,不仅仅是玩家的行为会调用这个Event。
例如:
- 玩家对玩家造成伤害 (发起受到伤害的是玩家)
- 玩家对生物造成伤害 (发起伤害的是玩家)
- 生物对玩家造成伤害 (受到伤害的是玩家)
- 玩家的箭对玩家造成伤害 (子实体)
这些都会调用这个方法。我们可以根据判断:发起伤害的实体:red_circle:getDamager()
和受到伤害的实体:red_circle:getEntity()
,只要其中有一个是未登录的玩家,就取消本次Event。
然后是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
@EventHandler public void onPlayerChat(PlayerChatEvent event) { Player player = event.getPlayer(); if (!plugin.isPlayerLogin(event.getPlayer())) { String message = event.getMessage(); if (message.equals("A_Bad_Password")) {
plugin.playerLogin(player); player.sendTitle("", "", 0, 0, 0);
player.sendMessage(player.getName() + ",欢迎~"); plugin.getServer().broadcastMessage("玩家:" + player.getName() + ",已加入游戏"); } else { player.sendMessage("抱歉,密码错误!"); } event.setCancelled(); } }
|
我们在这里编写了玩家聊天与输入密码的逻辑
先判断玩家是否已登录,然后通过:red_circle:event.getMessage()
获得消息,实际上也就是密码,来做对比。如果错误进行提示,如果正确则将其加入已登录玩家的列表中。
这时我们回头来看之前写的全部Event,如果该玩家登陆成功后,由于已被加入loginPlayerList
中,此时isPlayerLogin()
便会返回true,条件不成立,玩家的各种活动便不会被取消。
——发散一下思维,也可以在这里修改玩家发送的内容。加称号等等都是如此实现的。
六、当玩家离开
当玩家登录,我们把他保存在loginPlayerList
中。最后,当玩家离开,我们还需要将他移除,不然,如果一名玩家登录后下线,某人冒充顶替的话再上线的话,由于已登录列表中有他,就不需要登录了。
1 2 3 4 5 6 7
| @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { if (plugin.isPlayerLogin(event.getPlayer())) { plugin.playerLogout(event.getPlayer()); } }
|
七、效果
八、最后
一个简单的登录插件就编写完了。希望大家能够了解一些Event下面的方法,最重要的是去实践。无论是Eclipse还是IDEA,都自带了非常友好的提示功能。还有反编译。可以直接通过方法名,类名来了解Nukkit。
当然,这个插件还有一部分功能是残缺的,比如注册。有一些Event没有禁止掉,比如扔物品和捡起物品。这些都等待屏幕前的你来开发。
上一节 下一节