Skip to content

Latest commit

 

History

History
483 lines (406 loc) · 18 KB

AC-1-2.md

File metadata and controls

483 lines (406 loc) · 18 KB

AC-1-2 HarmonyAuth SMART Part 2

事件处理

我们接着上一节,上一节中我们已经完成了命令处理,并将数据保存在了 RuntimeDataManager 中。

接下来我们就应该监听事件啦~

我给我们的事件监听器起了个新名字:EventHarmony

移动

首先我们完成限制移动。

// EventHarmony 节选
public static Map<UUID, Float> originSpeed = new HashMap<>(); // 保存初始速度

@EventHandler
public void antiMove(PlayerMoveEvent e) {
    UUID id = e.getPlayer().getUniqueId();
    if (RuntimeDataManager.hasRestrictUUID(id)) {
        // 限制列表
        float oSpeed = e.getPlayer().getWalkSpeed();
        if (oSpeed > 0.0001) {
            originSpeed.put(id, oSpeed);
            e.getPlayer().setWalkSpeed((float) 0.0001);
            // 把速度设极小防止回弹
        }
        if (e.getFrom().distance(e.getTo()) != 0) {
            e.setCancelled(true);
            // 允许玩家转动视角
        }
    } else {
        if (originSpeed.containsKey(id)) {
            e.getPlayer().setWalkSpeed(originSpeed.get(id));
            originSpeed.remove(id);
            // 速度要复原
        }
    }
}

很简单,和之前基本一样。

物品栏,传送,受伤,交互

这些在登录前应该都禁止,直接取消掉就好了。

// EventHarmony 节选
@EventHandler
public void onPlayerInteract(PlayerInteractEvent e) {
    if (RuntimeDataManager.hasRestrictUUID(e.getPlayer().getUniqueId())) {
        e.setCancelled(true);
        e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint"));
    }
}

@EventHandler
public void noHurt(EntityDamageEvent e) {
    // 这是挨打!不是打人!
    if (e.getEntity() instanceof Player) {
        if (RuntimeDataManager.hasRestrictUUID(e.getEntity().getUniqueId())) {
            e.setCancelled(true);
            e.getEntity().sendMessage(Util.getAndTranslate("msg.hint"));
        }
    }
}

@EventHandler
public void noInteract(PlayerInteractEvent e) {
    if (RuntimeDataManager.hasRestrictUUID(e.getPlayer().getUniqueId())) {
        e.setCancelled(true);
        e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint"));
    }
}

@EventHandler
public void noBreak(BlockBreakEvent e) {
    if (RuntimeDataManager.hasRestrictUUID(e.getPlayer().getUniqueId())) {
        e.setCancelled(true);
        e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint"));
    }
}

@EventHandler
public void noInventory(InventoryOpenEvent e) {
    if (RuntimeDataManager.hasRestrictUUID(e.getPlayer().getUniqueId())) {
        e.setCancelled(true);
        e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint"));
    }
}

@EventHandler
public void noTeleport(PlayerTeleportEvent e) {
    if (RuntimeDataManager.hasRestrictUUID(e.getPlayer().getUniqueId())) {
        e.setCancelled(true);
        e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint"));
    }
}

基本上很简单,没有什么需要说明的。

聊天

聊天的处理遵循这个逻辑:

  • 在未登录模式下:

    • 若不以 /hl/L/l/reg/register/login/log/iforgot/ifg 开头,则直接取消。
    • 否则,放行。
  • 在已登录模式下:

    • 放行。
  • 在密码恢复模式 1(输入新密码)下:

    • 取消。如果不含空格,不为空且不以 / 开头,将新密码的哈希存入数据。否则重新提示一次。
    • 玩家如果已登录,退出密码恢复模式并应用新密码。
    • 否则,进入恢复模式 2 并提示。
  • 在密码恢复模式 2(输入理由)下:

    • 取消。如果不为空,将该数据保存并提示一次,随后将该玩家的 IForgotState 置为 true,退出恢复模式。否则重新提示输入。
  • 在审核模式(仅 OP)下:

    • 若不以 / 开头,取消。
  • 如果是表示「通过」,将该玩家的 IForgotState 置为 false,应用新密码,并将原因设为 <Internal> Accepted.。随后从数据库载入下一条。如果没有则退出。

    • 如果是表示「拒绝」,同样将 IForgotState 置为 false,原因设为 <Internal> Rejected.。随后从数据库载入下一条。如果没有则退出。
    • 如果是表示「退出」,将 OP 移出审核模式。

这里没有涉及命令处理的逻辑,那些已经完成了。

为了从数据库查询下一条,我们需要再次修改 FileDataManagerDBDataManager,以及 IDataManager 接口,我们先完成修改:

// IDataManager 节选
UUID getNextRequest();
// FileDataManager 节选
@Override
public UUID getNextRequest() {
    Set<String> keys = Objects.requireNonNull(data.getConfigurationSection("iforgot-states")).getKeys(false);
    for (String s : keys) {
        if (data.getBoolean("iforgot-states." + s)) {
            return UUID.fromString(s);
        }
    }
    return UUID.fromString("00000000-0000-0000-0000-000000000000");
}
// DBDataManager 节选
@Override
public UUID getNextRequest() {
    try {
        Connection connection = DriverManager.getConnection(db_url, username, password);
        Statement statement = connection.createStatement();
        ResultSet rsc = statement.executeQuery("SELECT COUNT(UUID) FROM harmony_auth_data WHERE IForgotState=true LIMIT 1");
        if (rsc.getInt(1) == 0) {
            return UUID.fromString("00000000-0000-0000-0000-000000000000");
        }
        // 没有还返回啥东西

        ResultSet rs = statement.executeQuery("SELECT UUID FROM harmony_auth_data WHERE IForgotState=true LIMIT 1");
        rs.first();
        String uString = rs.getString("UUID");
        return UUID.fromString(uString);
    } catch (SQLException e) {
        putError(e);
        return UUID.fromString("00000000-0000-0000-0000-000000000000");
        
    }
}

COUNT(列名) 是一个 SQL 函数,它表示「对应列的元素个数」。实际上我们只是要数一数有多少条恢复请求正在等待中,因此 COUNTUUID,实际上 COUNT 别的也是一样的。(COUNT 主键会快一点)

为了记录 OP 上次操作的是哪一个 UUID,我们设置了一个类变量:

// EventHarmony 节选
public static Map<UUID, UUID> lastJudgeUUID = new HashMap<>();

Map<K, V> 同样是个模板类,由于 Map 是「A to B」的结构,因此需要两个类型进行模板化,这里我们是从 UUID 映射到 UUID

哦对了,我们还要修改上次的命令处理,将上次处理的 UUID 放进去:

// CommandHandler 节选
RuntimeDataManager.toReadMode(id);
player.sendMessage(Util.getAndTranslate("msg.audit-in"));
UUID firstId = idm.getNextRequest();
EventHarmony.lastJudgeUUID.put(player.getUniqueId(), firstId);
// 这里!
if (firstId.equals(UUID.fromString("00000000-0000-0000-0000-000000000000"))) {
    RuntimeDataManager.exitReadMode(id);
    player.sendMessage(Util.getAndTranslate("msg.audit-out"));
} else {
    player.sendMessage(Util.getAndTranslate("audit-uuid" + firstId.toString()));
    player.sendMessage(Util.getAndTranslate("audit-reason" + idm.getIForgotManualReason(firstId)));
    player.sendMessage(Util.getAndTranslate("audit-hint"));

}

然后实现这个方法:

// EventHarmony 节选
public static final List<String> allowCmd = Arrays.asList("/hl", "/L", "/l", "/reg", "/register", "/login", "/log");

@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent e) {

    UUID id = e.getPlayer().getUniqueId();
    if (RuntimeDataManager.getIForgotMode(id) != 0) {
        // 恢复模式
        switch (RuntimeDataManager.getIForgotMode(id)) {
            case 1: // 等待输入新密码
                e.setCancelled(true);
                if (e.getMessage().split(" ").length != 1 || e.getMessage().startsWith("/")) {
                    e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-newpwd"));
                    return;
                }
                new BukkitRunnable() {
                    @Override
                    public void run() {
                        IDataManager idm;
                        if (HarmonyAuthSMART.instance.getConfig().getBoolean("mysql.enabled") && !HarmonyAuthSMART.dbError) {
                            idm = new DBDataManager();
                        } else {
                            idm = new FileDataManager();
                        }
                        if (!RuntimeDataManager.hasRestrictUUID(id)) {
                            // 已经登录,修改密码
                                idm.setPasswordHash(id, idm.getIForgotNewPasswordHash(id));
                                RuntimeDataManager.exitIForgotMode(id);
                                e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-accepted"));
                                return;
                            }
                            idm.setIForgotNewPasswordHash(id, Util.calculateMD5(e.getMessage()));
                        RuntimeDataManager.toIForgotMode(id, 2);
                        e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-hint"));
                    }
                }.runTaskAsynchronously(HarmonyAuthSMART.instance);

                break;
            case 2:
                // 等待输入原因
                e.setCancelled(true);
                if (e.getMessage().equals("")) {
                    e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-hint"));
                    return;
                }
                new BukkitRunnable() {
                    @Override
                    public void run() {
                        IDataManager idm;
                        if (HarmonyAuthSMART.instance.getConfig().getBoolean("mysql.enabled") && !HarmonyAuthSMART.dbError) {
                            idm = new DBDataManager();
                        } else {
                            idm = new FileDataManager();
                        }
                        idm.setIForgotManualReason(id, e.getMessage());
                        idm.setIForgotState(id, true);
                        RuntimeDataManager.exitIForgotMode(id);
                        e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-commit"));
                    }
                }.runTaskAsynchronously(HarmonyAuthSMART.instance);
                break;
        }
    } else {
        // 非恢复模式
        if (RuntimeDataManager.isInReadMode(id)) {
            // 审核模式
            if (e.getMessage().startsWith("/")) {
                if (!e.getMessage().startsWith("/iforgot")) {
                    return;
                } // 万一 OP 急需使用一个命令呢?
            }

            e.setCancelled(true);
            if (e.getMessage().toLowerCase().startsWith("q")) {
                RuntimeDataManager.exitReadMode(id);
                e.getPlayer().sendMessage(Util.getAndTranslate("msg.audit-out"));
            } else {
                new BukkitRunnable() {
                    @Override
                    public void run() {

                        IDataManager idm;
                        if (HarmonyAuthSMART.instance.getConfig().getBoolean("mysql.enabled") && !HarmonyAuthSMART.dbError) {
                            idm = new DBDataManager();
                        } else {
                            idm = new FileDataManager();
                        }
                        UUID jid = lastJudgeUUID.get(e.getPlayer().getUniqueId());
                        if (e.getMessage().toLowerCase().startsWith("y")) {
                            // 通过
                            idm.setPasswordHash(jid, idm.getIForgotNewPasswordHash(jid));
                            idm.setIForgotState(jid, false);
                            idm.setIForgotManualReason(jid, "<Internal> Accepted.");
                        } else if (e.getMessage().toLowerCase().startsWith("n")) {
                            // 拒绝
                            idm.setIForgotState(jid, false);
                            idm.setIForgotManualReason(jid, "<Internal> Rejected.");
                        } else {
                            RuntimeDataManager.exitReadMode(id);
                            e.getPlayer().sendMessage(Util.getAndTranslate("msg.audit-out"));
                            return;
                        }
                        Player player = e.getPlayer();
                        UUID nextId = idm.getNextRequest();
                        lastJudgeUUID.put(player.getUniqueId(), nextId);
                        // 发送下一条
                        if (nextId.equals(UUID.fromString("00000000-0000-0000-0000-000000000000"))) {
                            RuntimeDataManager.exitReadMode(id);
                            player.sendMessage(Util.getAndTranslate("msg.audit-out"));
                        } else {
                            player.sendMessage(Util.getAndTranslate("audit-uuid" + nextId.toString()));
                            player.sendMessage(Util.getAndTranslate("audit-reason" + idm.getIForgotManualReason(nextId)));
                            player.sendMessage(Util.getAndTranslate("audit-hint"));
                        }
                    }
                }.runTaskAsynchronously(HarmonyAuthSMART.instance);
            }
        } else {
            // 常规模式
            if (RuntimeDataManager.hasRestrictUUID(id)) {
                // 未登录时看看是不是允许的命令
                e.setCancelled(true);
                for (String a : allowCmd) {
                    if (e.getMessage().startsWith(a)) {
                        e.setCancelled(false);
                        break;
                    }
                }
            }
            // 已登录不做处理
        }
    }
}

很长,但是不难,相信你应该看得懂~

这里我们没有使用 clisti,因为大部分操作都在进入异步前做完了,即使玩家重复发送命令也不要紧。

登录

玩家进入服务器时,如果密码恢复请求被处理了,应该告知玩家。

如果最近一次登录在允许的时间内,则自动登录。否则,发送登录/注册提示。

// EventHarmony 节选
@EventHandler
public void onPlayerLogin(PlayerLoginEvent e) {
    UUID id = e.getPlayer().getUniqueId();
    new BukkitRunnable() {
        @Override
        public void run() {
            Date crDate = new Date();
            IDataManager idm;
            if (HarmonyAuthSMART.instance.getConfig().getBoolean("mysql.enabled") && !HarmonyAuthSMART.dbError) {
                idm = new DBDataManager();
            } else {
                idm = new FileDataManager();
            }
            if (idm.isExist(id)) {
                Date lLogin = idm.getLastLoginTime(id);
                double seconds = (crDate.getTime() - lLogin.getTime()) / 1000.0;
                // 转换为秒
                if (seconds <= HarmonyAuthSMART.instance.getConfig().getInt("auto-login")) {
                    RuntimeDataManager.removeRestrictUUID(id);
                    e.getPlayer().sendMessage(Util.getAndTranslate("msg.login-success"));
                    List<String> hooks = Util.generateHooks("hook.on-login-success", e.getPlayer().getName());
                    for (String cmd : hooks) {
                        Util.dispatchCommandAsServer(cmd);
                    }
                } else {
                    e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint-login"));
                    String ifState = idm.getIForgotManualReason(id);
                    if (ifState.equals("<Internal> Accepted.")) {
                        e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-accepted"));
                    } else if (ifState.equals("<Internal> Rejected.")) {
                        e.getPlayer().sendMessage(Util.getAndTranslate("msg.iforgot-rejected"));
                    }
                }
            } else {
                e.getPlayer().sendMessage(Util.getAndTranslate("msg.hint-register"));
            }
        }
    }.runTaskAsynchronously(HarmonyAuthSMART.instance);
}

如果不进行额外的设定,Date 对象就代表它被创建时的时间,也就是当前时间。

离开

只剩一点点了,在玩家离开时更新最后登录时间:

// EventHarmony 节选
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
    new BukkitRunnable() {
        @Override
        public void run() {
            IDataManager idm;
            if (HarmonyAuthSMART.instance.getConfig().getBoolean("mysql.enabled") && !HarmonyAuthSMART.dbError) {
                idm = new DBDataManager();
            } else {
                idm = new FileDataManager();
            }
            idm.setLastLoginTime(e.getPlayer().getUniqueId(), new Date());
        }
    }.runTaskAsynchronously(HarmonyAuthSMART.instance);
}

这样就完成了。

杂项工作

这里基本上就是启动时全部加载,关闭时全部保存,修改主类:

package rarityeg.harmonyauthsmart;

import org.bukkit.plugin.java.JavaPlugin;

import java.util.logging.Level;

public class HarmonyAuthSMART extends JavaPlugin {
    public static JavaPlugin instance;
    public static boolean dbError = false;

    @Override
    public void onEnable() {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            getLogger().log(Level.WARNING, "数据库驱动加载失败,将使用备用存储方法。");
            e.printStackTrace();
            dbError = true;
        }
        saveDefaultConfig();
        saveResource("data.yml", false);
        instance = this;
        new DBDataManager().loadAll();
        new FileDataManager().loadAll();
        
    }

    @Override
    public void onDisable() {
        new DBDataManager().saveAll();
        new FileDataManager().saveAll();
    }
}

准备调试

创建工件,这一次除了「'HarmonyAuth SMART compile output'」以外,还要添加「'mysql-connector-java-8.0.23'」,因为 Bukkit 本身不提供该驱动。

记得勾选「Include in project build」,「Apply」、「OK」。

回到代码页面,按下那个我们已经按过多次的按钮(构建)~

结束了……?