查看: 258|回復: 0

[閒聊閒語] L1AutoMoveService

[複製鏈接]

71

主題

267

帖子

5707

金錢

火焰之影

Rank: 8Rank: 8

威望
247
精華
0
貢獻
0
鑽石
0
閱讀權限
50
積分
6468
在線時間
110 小時
相冊
0
日誌
0
好友
0
發表於 2026-1-13 18:29 | 顯示全部樓層 |閱讀模式
package l1j.server.server.model.Instance;

import l1j.server.server.ClientThread;
import l1j.server.server.clientpackets.C_MoveChar;
import l1j.server.server.serverpackets.S_MoveCharPacket;
import l1j.server.server.serverpackets.S_NPCPack;

public class L1AutoMoveService {

    // 8個方向的位置偏移量(必須與 C_MoveChar.java 中的一致)
    private static final int[] HEADING_TABLE_X = {0, 1, 1, 1, 0, -1, -1, -1};
    private static final int[] HEADING_TABLE_Y = {-1, -1, 0, 1, 1, 1, 0, -1};

    /**
     * 台灣版移動服務 - 修正怪物消失問題
     * 台灣版協議:客戶端只發送方向,不發送目標座標
     */
    public static void moveToTarget(L1PcInstance pc, int targetX, int targetY) {
        if (pc == null || pc.isDead() || pc.isParalyzed() || pc.isTeleport() || pc.isInvisble()) {
            return;
        }

        // 1. 計算最佳移動方向
        int dir = calculateBestDirection(pc, targetX, targetY);
        if (dir == -1) {
            return; // 沒有可通行的方向
        }

        // 2. 計算移動後的位置
        int nextX = pc.getX() + HEADING_TABLE_X[dir];
        int nextY = pc.getY() + HEADING_TABLE_Y[dir];

        // 3. 檢查是否可以通行
        if (!pc.getMap().isPassable(nextX, nextY, pc)) {
            // 嘗試其他方向
            dir = findAlternativeDirection(pc, dir);
            if (dir == -1) {
                return;
            }
            nextX = pc.getX() + HEADING_TABLE_X[dir];
            nextY = pc.getY() + HEADING_TABLE_Y[dir];
        }

        // 4. 【關鍵】保存移動前的座標,用於視野更新
        int oldX = pc.getX();
        int oldY = pc.getY();

        // 5. 【關鍵】立即更新伺服器端的位置
        pc.getLocation().set(nextX, nextY);
        pc.setHeading(dir);

        // 6. 【關鍵】發送移動封包給客戶端(台灣版格式)
        sendTaiwanMovePacket(pc, dir);

        // 7. 【關鍵】廣播位置更新給其他玩家
        broadcastPositionUpdate(pc);

        // 8. 【關鍵】如果位置改變了,更新視野內的物件
        if (oldX != nextX || oldY != nextY) {
            updateVisibleObjects(pc, oldX, oldY);
        }
    }

    /**
     * 計算最佳移動方向(考慮障礙物)
     */
    private static int calculateBestDirection(L1PcInstance pc, int targetX, int targetY) {
        int currentX = pc.getX();
        int currentY = pc.getY();

        // 計算原始方向
        int idealDir = calculateDirection(currentX, currentY, targetX, targetY);

        // 檢查原始方向是否可通行
        int nextX = currentX + HEADING_TABLE_X[idealDir];
        int nextY = currentY + HEADING_TABLE_Y[idealDir];

        if (pc.getMap().isPassable(nextX, nextY, pc)) {
            return idealDir;
        }

        // 如果不可通行,尋找最佳替代方向
        return findAlternativeDirection(pc, idealDir);
    }

    /**
     * 計算基本方向
     */
    private static int calculateDirection(int currentX, int currentY, int targetX, int targetY) {
        int dx = targetX - currentX;
        int dy = targetY - currentY;

        // 標準的8方向計算
        if (dx == 0 && dy < 0) return 0;      // 上
        if (dx > 0 && dy < 0) return 1;       // 右上
        if (dx > 0 && dy == 0) return 2;      // 右
        if (dx > 0 && dy > 0) return 3;       // 右下
        if (dx == 0 && dy > 0) return 4;      // 下
        if (dx < 0 && dy > 0) return 5;       // 左下
        if (dx < 0 && dy == 0) return 6;      // 左
        if (dx < 0 && dy < 0) return 7;       // 左上

        return 0; // 預設向上
    }

    /**
     * 尋找替代方向
     */
    private static int findAlternativeDirection(L1PcInstance pc, int originalDir) {
        // 優先嘗試的方向順序:原始方向 -> 左右45度 -> 左右90度 -> 反向
        int[] tryOrder = {
            originalDir,                    // 原始方向
            (originalDir + 1) % 8,          // 右45度
            (originalDir + 7) % 8,          // 左45度
            (originalDir + 2) % 8,          // 右90度
            (originalDir + 6) % 8,          // 左90度
            (originalDir + 4) % 8,          // 相反方向
            (originalDir + 3) % 8,          // 右135度
            (originalDir + 5) % 8           // 左135度
        };

        for (int dir : tryOrder) {
            int nextX = pc.getX() + HEADING_TABLE_X[dir];
            int nextY = pc.getY() + HEADING_TABLE_Y[dir];

            if (pc.getMap().isPassable(nextX, nextY, pc)) {
                return dir;
            }
        }

        return -1; // 沒有可通行的方向
    }

    /**
     * 【關鍵】發送台灣版移動封包
     * 台灣版:只發送方向,不發送目標座標
     */
    private static void sendTaiwanMovePacket(L1PcInstance pc, int dir) {
        ClientThread client = pc.getNetConnection();
        if (client == null) {
            return;
        }

        try {
            // 台灣版封包結構:只需要發送方向
            byte[] data = new byte[6];

            // 【關鍵】台灣版加密:方向 XOR 0x49
            int encryptedDir = dir ^ 0x49;

            // 填充封包(台灣版C_MoveChar會忽略座標,只讀取方向)
            data[5] = (byte) encryptedDir;

            // 發送移動封包
            new C_MoveChar(data, client);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 廣播位置更新
     */
    private static void broadcastPositionUpdate(L1PcInstance pc) {
        try {
            S_MoveCharPacket packet = new S_MoveCharPacket(pc);

            // 根據玩家狀態決定如何廣播
            if (pc.isGmInvis() || pc.isGhost()) {
                // GM隱身或鬼魂模式不廣播
            } else if (pc.isInvisble()) {
                pc.broadcastPacketForFindInvis(packet, true);
            } else {
                pc.broadcastPacket(packet);
            }

            // 更新自己的位置顯示
            pc.sendPackets(packet);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 【關鍵】更新可見物件 - 解決怪物消失問題
     */
    private static void updateVisibleObjects(L1PcInstance pc, int oldX, int oldY) {
        try {
            // 獲取玩家周圍的所有物件
            java.util.List<l1j.server.server.model.L1Object> allObjects =
                l1j.server.server.model.L1World.getInstance().getVisibleObjects(pc, 20); // 稍微擴大範圍

            // 重新發送所有可見怪物的顯示封包
            for (l1j.server.server.model.L1Object obj : allObjects) {
                if (obj instanceof L1MonsterInstance) {
                    L1MonsterInstance monster = (L1MonsterInstance) obj;

                    // 檢查怪物是否有效且在畫面內
                    if (isValidMonster(monster) && isInScreen(pc, monster)) {
                        // 【關鍵】重新發送怪物顯示封包
                        S_NPCPack packet = new S_NPCPack(monster);
                        pc.sendPackets(packet);

                        // 如果怪物對其他玩家也可見,也更新他們
                        if (!monster.isDead() && !monster.isInvisble()) {
                            monster.broadcastPacket(packet);
                        }
                    }
                }
            }

            // 【重要】觸發視野更新
            pc.updateObject();

            // 更新隱身偵測
            pc.broadcastPacketForFindInvisibility();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 檢查怪物是否有效
     */
    private static boolean isValidMonster(L1MonsterInstance monster) {
        return monster != null &&
               !monster.isDead() &&
               !monster.isInvisble() &&
               monster.getCurrentHp() > 0;
    }

    /**
     * 檢查物件是否在畫面內
     */
    private static boolean isInScreen(L1PcInstance pc, l1j.server.server.model.L1Object obj) {
        if (pc == null || obj == null) return false;

        int screenRange = 16; // 稍微擴大範圍確保顯示

        int dx = Math.abs(pc.getX() - obj.getX());
        int dy = Math.abs(pc.getY() - obj.getY());

        return dx <= screenRange && dy <= screenRange;
    }

    /**
     * 直接設定位置(用於瞬移或位置修正)
     */
    public static void setLocation(L1PcInstance pc, int x, int y, int heading) {
        if (pc == null) return;

        // 保存舊位置
        int oldX = pc.getX();
        int oldY = pc.getY();

        // 更新位置
        pc.getLocation().set(x, y);
        pc.setHeading(heading);

        // 廣播位置更新
        broadcastPositionUpdate(pc);

        // 更新視野物件
        if (oldX != x || oldY != y) {
            updateVisibleObjects(pc, oldX, oldY);
        }
    }

    /**
     * 計算兩點之間的 Chebyshev 距離(天堂標準)
     */
    public static int calculateDistance(int x1, int y1, int x2, int y2) {
        int dx = Math.abs(x1 - x2);
        int dy = Math.abs(y1 - y2);
        return Math.max(dx, dy);
    }

    /**
     * 檢查是否可以移動到目標
     */
    public static boolean canMoveTo(L1PcInstance pc, int targetX, int targetY) {
        int dir = calculateDirection(pc.getX(), pc.getY(), targetX, targetY);
        int nextX = pc.getX() + HEADING_TABLE_X[dir];
        int nextY = pc.getY() + HEADING_TABLE_Y[dir];

        return pc.getMap().isPassable(nextX, nextY, pc);
    }
}





上一篇︰2026-01-13 搖一搖!
下一篇︰L1AutoHuntHandler
您需要登錄後才可以回帖 登錄 | 註冊會員

本版積分規則

天堂私服列表

45客服

Archiver| 45天堂私服論壇   分享到微博! 分享到臉書! 分享到噗浪! 分享到維特! 分享到Google+! 分享到LINE!

45天堂私服發佈站 ©    天堂私服架設教學  提供最新天堂私服最新資訊

流量最高、品質最好、服務最優、玩家首選、最新天堂私服資訊,都在45天堂私服發佈站.    免責聲明

Sitetag
line客服聯繫
掃一掃二碼
Line客服聯繫
24H專人回覆
返回頂部 返回列表