esdion 發表於 2026-1-13 18:29

L1AutoMoveService

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;
      int nextY = pc.getY() + HEADING_TABLE_Y;

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

      // 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;
      int nextY = currentY + HEADING_TABLE_Y;

      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;
            int nextY = pc.getY() + HEADING_TABLE_Y;

            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;

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

            // 填充封包(台灣版C_MoveChar會忽略座標,只讀取方向)
            data = (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;
      int nextY = pc.getY() + HEADING_TABLE_Y;

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

頁: [1]
查看完整版本: L1AutoMoveService