All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.entity.ai.route.finder.impl.SimpleFlatAStarRouteFinder Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.entity.ai.route.finder.impl;

import cn.nukkit.Player;
import cn.nukkit.Server;
import cn.nukkit.api.PowerNukkitXOnly;
import cn.nukkit.api.Since;
import cn.nukkit.block.Block;
import cn.nukkit.entity.EntityIntelligent;
import cn.nukkit.entity.ai.EntityAI;
import cn.nukkit.entity.ai.route.data.Node;
import cn.nukkit.entity.ai.route.finder.SimpleRouteFinder;
import cn.nukkit.entity.ai.route.posevaluator.IPosEvaluator;
import cn.nukkit.level.Level;
import cn.nukkit.math.Vector3;
import cn.nukkit.math.VectorMath;
import cn.nukkit.network.protocol.SpawnParticleEffectPacket;
import cn.nukkit.utils.Utils;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;
import java.util.*;

/**
 * 标准A*寻路实现
 */
@PowerNukkitXOnly
@Since("1.6.0.0-PNX")
@Getter
@Setter
public class SimpleFlatAStarRouteFinder extends SimpleRouteFinder {

    //这些常量是为了避免开方运算而设置的
    //直接移动成本
    protected final static int DIRECT_MOVE_COST = 10;
    //倾斜移动成本
    protected final static int OBLIQUE_MOVE_COST = 14;

    protected final PriorityQueue openList = new PriorityQueue<>();

    protected final List closeList = new ArrayList<>();
    protected final HashSet closeHashSet = new HashSet<>();

    protected EntityIntelligent entity;

    protected Level level;

    protected Vector3 start;

    protected Vector3 target;

    protected Vector3 reachableTarget;

    protected boolean finished = false;
    protected boolean searching = false;

    protected boolean interrupt = false;

    protected boolean reachable = true;

    protected boolean enableFloydSmooth = true;

    //寻路最大深度
    protected int currentSearchDepth = 100;

    protected int maxSearchDepth = 100;

    public SimpleFlatAStarRouteFinder(IPosEvaluator blockEvaluator, EntityIntelligent entity) {
        super(blockEvaluator);
        this.entity = entity;
        this.level = entity.level;
    }

    @Override
    public void setStart(Vector3 vector3) {
        this.start = vector3;
        if (isInterrupt()) this.setInterrupt(true);
    }

    @Override
    public void setTarget(Vector3 vector3) {
        this.target = vector3;
        if (isInterrupt()) this.setInterrupt(true);
    }

    @Override
    public boolean isSearching() {
        return searching;
    }

    @Override
    public boolean search() {
        //init status
        this.finished = false;
        this.searching = true;
        this.interrupt = false;
        var currentReachable = true;
        //若实体未处于active状态,则关闭路径平滑
        this.setEnableFloydSmooth(this.entity.isActive());
        //清空openList和closeList
        openList.clear();
        closeList.clear();
        closeHashSet.clear();
        //重置寻路深度
        currentSearchDepth = maxSearchDepth;

        //将起点放置到closeList中,以开始寻路
        //起点没有父节点,且我们不需要计算他的代价
        Node currentNode = new Node(start, null, 0, 0);
        var tmpNode = new Node(start, null, 0, 0);
        closeList.add(tmpNode);
        closeHashSet.add(tmpNode.getVector3());

        //若当前寻路点没有到达终点
        while (!isPositionOverlap(currentNode.getVector3(), target)) {
            //检查是否被中断了
            if (this.isInterrupt()) {
                currentSearchDepth = 0;
                this.searching = false;
                this.finished = true;
                this.reachable = false;
                return false;
            }
            //将当前节点周围的有效节点放入openList中
            putNeighborNodeIntoOpen(currentNode);
            //若未超出寻路深度,则获取代价最小的一个node并将其设置为currentNode
            if (openList.peek() != null && currentSearchDepth-- > 0) {
                closeList.add(currentNode = openList.poll());
                closeHashSet.add(currentNode.getVector3());
            } else {
                this.searching = false;
                this.finished = true;
                currentReachable = false;
                break;
            }
        }

        //因为在前面是否到达终点的检查中我们只粗略检查了坐标的floor值
        //所以说这里我们还需要将其精确指向到终点
        Node targetNode = currentNode;
        if (!currentNode.getVector3().equals(target)) {
            targetNode = new Node(target, currentNode, 0, 0);
        }

        //如果无法到达,则取最接近终点的一个Node作为尾节点
        Node reachableNode = null;
        reachableTarget = currentReachable ? target : (reachableNode = getNearestNodeFromCloseList(target)).getVector3();
        List findingPath = currentReachable ? getPathRoute(targetNode) : getPathRoute(reachableNode);
        //使用floyd平滑路径
        if (enableFloydSmooth)
            findingPath = floydSmooth(findingPath);

        //清空上次的寻路结果
        this.resetNodes();
        //重置Node指针
        this.setNodeIndex(0);

        //写入结果
        this.addNode(findingPath);

        if (EntityAI.DEBUG) {
            findingPath.forEach(node -> {
                sendParticle("minecraft:balloon_gas_particle", node.getVector3(), Server.getInstance().getOnlinePlayers().values().toArray(Player.EMPTY_ARRAY));
            });
        }

        this.reachable = currentReachable;
        this.finished = true;
        this.searching = false;

        return true;
    }

    protected void sendParticle(String identifier, Vector3 pos, Player[] showPlayers) {
        SpawnParticleEffectPacket packet = new SpawnParticleEffectPacket();
        packet.identifier = identifier;
        packet.dimensionId = this.entity.level.getDimension();
        packet.position = pos.asVector3f();
        Arrays.stream(showPlayers).forEach(player -> {
            if (!player.isOnline())
                return;
            try {
                player.dataPacket(packet);
            } catch (Throwable ignore) {}
        });
    }

    /**
     * 获取指定位置的方块的移动Cost
     *
     * @param level
     * @param pos
     * @return cost
     */
    protected int getBlockMoveCostAt(@NotNull Level level, Vector3 pos) {
        return level.getTickCachedBlock(pos).getWalkThroughExtraCost() + level.getTickCachedBlock(pos.add(0, -1, 0)).getWalkThroughExtraCost();
    }

    /**
     * 将一个节点周围的有效节点放入OpenList中
     *
     * @param node 节点
     */
    protected void putNeighborNodeIntoOpen(@NotNull Node node) {
        boolean N, E, S, W;

        Vector3 vector3 = new Vector3(node.getVector3().getFloorX() + 0.5, node.getVector3().getY(), node.getVector3().getFloorZ() + 0.5);

        double offsetY;

        if ((offsetY = getAvailableHorizontalOffset(vector3)) != -384) {
            if (Math.abs(offsetY) > 0.25) {
                Vector3 vec = vector3.add(1, offsetY, 0);
                if (!existInCloseList(vec)) {
                    Node nodeNear = getOpenNode(vec);
                    if (nodeNear == null) {
                        this.openList.offer(new Node(vec, node, node.getG(), calH(vec, target)));
                    } else {
                        if (node.getG() < nodeNear.getG()) {
                            nodeNear.setParent(node);
                            nodeNear.setG(node.getG());
                            nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                        }
                    }
                }
            }
        }

        if (E = ((offsetY = getAvailableHorizontalOffset(vector3.add(1, 0, 0))) != -384)) {
            Vector3 vec = vector3.add(1, offsetY, 0);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + DIRECT_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        if (S = ((offsetY = getAvailableHorizontalOffset(vector3.add(0, 0, 1))) != -384)) {
            Vector3 vec = vector3.add(0, offsetY, 1);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + DIRECT_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        if (W = ((offsetY = getAvailableHorizontalOffset(vector3.add(-1, 0, 0))) != -384)) {
            Vector3 vec = vector3.add(-1, offsetY, 0);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + DIRECT_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        if (N = ((offsetY = getAvailableHorizontalOffset(vector3.add(0, 0, -1))) != -384)) {
            Vector3 vec = vector3.add(0, offsetY, -1);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + DIRECT_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        //我们不允许实体在上下坡的时候斜着走,因为这容易导致实体卡脚(原版也是这个逻辑)
        //接触水的时候就不需要这么判断了
        if (N && E && (((offsetY = getAvailableHorizontalOffset(vector3.add(1, 0, -1))) == 0) || (offsetY != -384 && entity.isTouchingWater()))) {
            Vector3 vec = vector3.add(1, offsetY, -1);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + OBLIQUE_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        if (E && S && (((offsetY = getAvailableHorizontalOffset(vector3.add(1, 0, 1))) == 0) || (offsetY != -384 && entity.isTouchingWater()))) {
            Vector3 vec = vector3.add(1, offsetY, 1);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + OBLIQUE_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        if (W && S && (((offsetY = getAvailableHorizontalOffset(vector3.add(-1, 0, 1))) == 0) || (offsetY != -384 && entity.isTouchingWater()))) {
            Vector3 vec = vector3.add(-1, offsetY, 1);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + OBLIQUE_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }

        if (W && N && (((offsetY = getAvailableHorizontalOffset(vector3.add(-1, 0, -1))) == 0) || (offsetY != -384 && entity.isTouchingWater()))) {
            Vector3 vec = vector3.add(-1, offsetY, -1);
            if (!existInCloseList(vec)) {
                var cost = getBlockMoveCostAt(level, vec) + OBLIQUE_MOVE_COST + node.getG();
                Node nodeNear = getOpenNode(vec);
                if (nodeNear == null) {
                    this.openList.offer(new Node(vec, node, cost, calH(vec, target)));
                } else {
                    if (cost < nodeNear.getG()) {
                        nodeNear.setParent(node);
                        nodeNear.setG(cost);
                        nodeNear.setF(nodeNear.getG() + nodeNear.getH());
                    }
                }
            }
        }
    }

    protected Node getOpenNode(Vector3 vector2) {
        for (Node node : this.openList) {
            if (vector2.equals(node.getVector3())) {
                return node;
            }
        }

        return null;
    }

    protected boolean existInOpenList(Vector3 vector2) {
        return getOpenNode(vector2) != null;
    }

    protected Node getCloseNode(Vector3 vector2) {
        for (Node node : this.closeList) {
            if (vector2.equals(node.getVector3())) {
                return node;
            }
        }
        return null;
    }

    protected boolean existInCloseList(Vector3 vector2) {
        return closeHashSet.contains(vector2);
    }


    /**
     * 计算当前点到终点的代价H
     * 默认使用对角线+直线距离
     */
    protected int calH(Vector3 start, Vector3 target) {
        //使用DIRECT_MOVE_COST和OBLIQUE_MOVE_COST计算代价
        //计算对角线距离
        int obliqueCost = (int) (Math.abs(Math.min(target.x - start.x, target.z - start.z)) * OBLIQUE_MOVE_COST);
        //计算剩余直线距离
        int directCost = (int) ((Math.abs(Math.max(target.x - start.x, target.z - start.z)) - Math.abs(Math.min(target.x - start.x, target.z - start.z))) * DIRECT_MOVE_COST);
        return obliqueCost + directCost + (int) (Math.abs(target.y - start.y) * DIRECT_MOVE_COST);
    }

    /**
     * 获取目标坐标最高有效点(沿Y轴往下检查)
     */
    protected Block getHighestUnder(Vector3 vector3, int limit) {
        if (limit > 0) {
            for (int y = vector3.getFloorY(); y >= vector3.getFloorY() - limit; y--) {
                Block block = this.level.getTickCachedBlock(vector3.getFloorX(), y, vector3.getFloorZ(), false);
                if (evalStandingBlock(block))
                    return block;
            }
            return null;
        }
        for (int y = vector3.getFloorY(); y >= -64; y--) {
            Block block = this.level.getTickCachedBlock(vector3.getFloorX(), y, vector3.getFloorZ(), false);
            if (evalStandingBlock(block))
                return block;
        }
        return null;
    }

    /**
     * 指定位置是否可作为一个有效的节点
     */
    protected boolean evalPos(Vector3 pos) {
        return evalPos.evalPos(entity, pos);
    }

    /**
     * 指定方块上面是否可作为一个有效的节点
     */
    protected boolean evalStandingBlock(Block block) {
        return evalPos.evalStandingBlock(entity, block);
    }

    /**
     * @param vector3
     * @return 指定坐标可到达的最高点 (limit=4)
     */
    protected int getAvailableHorizontalOffset(Vector3 vector3) {
        var block = getHighestUnder(vector3, 4);
        if (block != null) {
            return block.getFloorY() - vector3.getFloorY() + 1;
        }
        return -384;
    }

    protected boolean hasBarrier(Node node1, Node node2) {
        return hasBarrier(node1.getVector3(), node2.getVector3());
    }

    /**
     * 指定两个Node之间是否有障碍物
     */
    protected boolean hasBarrier(Vector3 pos1, Vector3 pos2) {
        if (pos1.equals(pos2)) return false;
        return VectorMath.getPassByVector3(pos1, pos2).stream().anyMatch(
                (pos) -> {
                    var offsetX = pos.x - this.entity.x;
                    var offsetY = pos.y - this.entity.y;
                    var offsetZ = pos.z - this.entity.z;
                    var offsetBox = this.entity.getBoundingBox().getOffsetBoundingBox(offsetX, offsetY, offsetZ);
                    return Utils.hasCollisionBlocks(this.level, offsetBox);
                }
        );
    }

    /**
     * 使用Floyd算法平滑A*路径
     */
    protected List floydSmooth(List array) {
        int current = 0;
        int total = 2;
        if (array.size() > 2) {
            while (total < array.size()) {
                if (hasBarrier(array.get(current), array.get(total)) || total == array.size() - 1) {
                    array.get(total - 1).setParent(array.get(current));
                    current = total - 1;
                }
                total++;
            }
            Node temp = array.get(array.size() - 1);
            List tempL = new ArrayList<>();
            tempL.add(temp);
            while (temp.getParent() != null) {
                tempL.add((temp = temp.getParent()));
            }
            Collections.reverse(tempL);
            return tempL;
        }
        return array;
    }

    /**
     * 将Node链转换成List样式的路径信息
     *
     * @param end 列表尾节点
     */
    protected List getPathRoute(@Nullable Node end) {
        List nodes = new ArrayList<>();
        if (end == null)
            end = closeList.get(closeList.size() - 1);
        nodes.add(end);
        if (end.getParent() != null) {
            while (!end.getParent().getVector3().equals(start)) {
                nodes.add(end = end.getParent());
            }
            nodes.add(end.getParent());
        } else {
            nodes.add(end);
        }
        Collections.reverse(nodes);
        return nodes;

    }

    /**
     * 获取接近指定坐标的最近的Node
     */
    protected Node getNearestNodeFromCloseList(Vector3 vector3) {
        double min = Double.MAX_VALUE;
        Node node = null;
        for (Node n : closeList) {
            double distanceSquared = n.getVector3().floor().distanceSquared(vector3.floor());
            if (distanceSquared < min) {
                min = distanceSquared;
                node = n;
            }
        }
        return node;
    }

    /**
     * 坐标是否重叠了 
* 此方法只会比较坐标的floorX、floorY、floorZ */ protected boolean isPositionOverlap(Vector3 vector2, Vector3 vector2_) { return vector2.getFloorX() == vector2_.getFloorX() && vector2.getFloorZ() == vector2_.getFloorZ() && vector2.getFloorY() == vector2_.getFloorY(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy